Friday, May 22, 2026

Microsoft Security success stories: How St. Luke’s and ManpowerGroup are securing AI foundations

AI is reshaping how work gets done—and how risks emerge across cloud, data, identity, and more. Many organizations want AI-powered productivity, but their security foundations aren’t yet built for it. As organizations move toward AI-powered operating models, security becomes the critical enabler to allow innovation to scale responsibly. In this new era of agentic AI,1 protections can’t be layered on after the fact; they must be built into the fabric of how AI systems are developed, governed, and used—grounded in strong cloud security posture, clear data governance, and Zero Trust principles that assume breach and verify continuously.  We’re sharing two customer spotlights that explore how global organizations are putting that approach into practice.

Why security has become a strategic enabler for AI‑powered growth 

These customer stories highlight how security is no longer a supporting function—it’s a strategic enabler of growth, speed, and trust. As AI accelerates decision-making and reshapes how work gets done, leaders must modernize without increasing risk or slowing the business. The experiences of these forward-looking organizations reflect the realities many companies face: gaining consistent visibility across complex environments, moving faster while maintaining trust, meeting governance and compliance expectations that expand with AI adoption, and driving operational efficiency through automation. These examples will show how the right security foundation allows organizations to scale AI with confidence—turning protection into a competitive advantage, not a constraint.  

First, we’ll take a closer look at St. Luke’s University Health Network. 

How St. Luke’s is accelerating efficiency and threat response with AI 

St. Luke’s identified a critical gap in unified, real-time visibility across its security tools, limiting its ability to detect and stop threats early. The organization needed a way to see across their entire landscape and respond to threats as they emerge. To modernize and unify security operations, St. Luke’s turned to Microsoft Security Copilot to supercharge analyst productivity and help its Security Operations Center (SOC) teams operate at scale. 

By connecting Microsoft Defender and Microsoft Sentinel, St. Luke’s gains a single, AI-powered view across endpoints, identity, email, and cloud workloads—helping analysts move faster, correlate cyberthreats more effectively, and shift from reactive response to proactive, predictive defense. With AI embedded directly into daily workflows, teams can identify risks in real time, uncover gaps in visibility, and make more informed decisions with greater precision.

Streamlining workflows and automating protection

At the same time, Security Copilot agents are transforming how the SOC operates by automating time-consuming tasks like alert triage and vulnerability remediation. This reduces noise, accelerates investigations, and frees analysts to focus on real threats and strategic work. The result is a more efficient, collaborative, and resilient security operation built for today’s increasingly complex threat landscape. With Microsoft Security Copilot, St. Luke’s has:

  • Unified visibility across Defender and Microsoft Sentinel eliminates silos and accelerates threat response.
  • AI-powered insights help analysts detect, investigate, and act on cyberthreats in real time.
  • Security Copilot agents automating routine tasks, with Security Triage Agent saving up to 200 analyst hours each month.
  • Advanced phishing triage reduces false positives and improves decision confidence.
  • Centralized workflows improve collaboration, reporting speed, and overall SOC efficiency.

St. Luke’s sees its investment in Security Copilot as the foundation for a self-improving security ecosystem. AI-powered security means the team stays ahead of both technological and business changes, ensuring that St. Luke’s remains resilient in the face of evolving threats. To learn more about how St. Luke’s is modernizing and unifying security operations with Microsoft Security Copilot, watch the customer video or read the full St. Luke’s customer story.

How ManpowerGroup is securing a global workforce with a unified platform 

ManpowerGroup is modernizing toward a unified, cloud-based security platform to protect a highly distributed workforce, addressing identity-centric risk and complex compliance requirements as AI becomes embedded in everyday work. Their experiences show how organizations can use Microsoft Security to secure the foundation of AI transformation, end to end. 

As ManpowerGroup scaled globally, its longstanding mix of security tools became more difficult to manage, driving complexity, inconsistent controls, and slower response as cyberthreats and regulatory demands increased. 

To reduce tool sprawl, ManpowerGroup deployed Microsoft 365 E5 for the real-time identity, endpoint, email, and cloud prevention, detection, and response capabilities of Microsoft Defender, plus the cloud-native security information and event management (SIEM) and security orchestration, automation, and response (SOAR) performance of Microsoft Sentinel

By deploying Microsoft 365 E5, ManpowerGroup reduced security complexity, cut integration timelines from weeks or months to hours or days, unified global security operations, and built an AI-ready security foundation. To see how this platform approach is supporting secure, agile operations worldwide, watch the customer video read the full ManpowerGroup story

A repeatable playbook for securing AI at scale 

While these customers operate in very different environments, their paths to securing their organization and adopting (or preparing to adopt) AI followed the same core pattern—one that other organizations can adopt as they modernize. Both started by anchoring security decisions in business risk, then unified signals across cloud, data, identity, and operations, and finally automated guardrails so protection could scale alongside AI-powered work. These experiences point to a clear, repeatable approach for security and adopting AI without slowing business: 

  • Lead with risk and business value. Clearly define what must be protected—and why—so security enables AI adoption rather than constraining it. 
  • Unify visibility across the environment. Connect cloud, identity, data, and security operations (SecOps) signals into a single operational view to reduce blind spots. 
  • Make governance real, not aspirational. Operationalize classification, labeling, data loss prevention, and policy enforcement, so protections are consistent by default. 
  • Harden posture continuously. Use continuous configuration management and drift detection to prevent misconfigurations as environments evolve. 
  • Automate outcomes at scale. Streamline response and compliance reporting so security and governance improve without increasing headcount. 

This approach helped both organizations move faster with confidence—and offers a practical blueprint for others looking to secure the foundation of AI transformation. 

What Frontier firms get right in the AI era 

These stories point to a broader pattern emerging among leading organizations. “Frontier firms” refers to organizations that lead in the AI era by pairing speed with trust. They move quickly—but not recklessly—because security is treated as a foundational capability, not an afterthought. For these organizations, protection is built into how work gets done: governance that scales as AI adoption grows, posture that remains resilient as environments change, and controls that operate continuously in the background. Security becomes the primitive that allows AI to be deployed with confidence, not constraint. 

These customers exemplify what this looks like in practice. And through their stories, we gain a playbook that other organizations can deploy with confidence. By modernizing security as a platform—connecting visibility, governance, posture management, and automation—organizations can enable AI-powered work while strengthening trust across data, identities, cloud environments, and more. These customer stories show that in the AI era, organizations that treat security as a strategic foundation will be best positioned to lead, adapt, and compete in an AI-powered world. Learn more about how Microsoft Security helps organizations secure AI-powered work at scale. 

Are you a regular user of Microsoft Defender for Cloud? Share your insights and experiences on Gartner Peer Insights.™

Learn more

Learn more about Microsoft Defender for Cloud, Microsoft Purview, and Zero Trust.

To learn more about Microsoft Security solutions, visit our website. Bookmark the Security blog to keep up with our expert coverage on security matters. Also, follow us on LinkedIn (Microsoft Security) and X (@MSFTSecurity) for the latest news and updates on cybersecurity.  


1Secure agentic AI for your Frontier Transformation, Microsoft Security blog. March 9, 2026.

The post Microsoft Security success stories: How St. Luke’s and ManpowerGroup are securing AI foundations appeared first on Microsoft Security Blog.



from Microsoft Security Blog https://ift.tt/oesaj4z
via IFTTT

The Good, the Bad and the Ugly in Cybersecurity – Week 21

The Good | Joint Operations Dismantle Cybercrime Infrastructure, Infostealers & Malicious VPNs

Over 200 individuals and another 382 suspects have been rounded up in Interpol’s Operation Ramz, an initiative targeting cybercrime networks across the Middle East and North Africa.

Spanning thirteen countries and working alongside cybersecurity partners, police seized 53 servers used for malware distribution, phishing campaigns, and online fraud responsible for attacks with at least 3867 confirmed victims.

The third major crackdown organized by Interpol this year, highlights of the operation include dismantling an investment scam in Jordan and a phishing-as-a-service (PHaaS) platform in Algeria, and confiscating devices, servers, and data linked to various operations in Qatar, Oman, and Morocco.

Ukrainian cyberpolice, alongside U.S. law enforcement, have identified a suspect in Odesa allegedly responsible for operating an infostealer malware campaign. Between 2024 and 2025, the accused targeted users of a California-based online store, compromising 28,000 customer accounts. He then exploited 5,800 of these stolen session tokens to make $721,000 in unauthorized purchases.

The suspect managed the digital infrastructure required to harvest, process, and sell the stolen account credentials through specialized online forums and Telegram bots. As authorities continue to build the formal charge, they have seized several phones, bank cards, and other digital evidence confirming his involvement in the attacks.

Europol has taken “First VPN”, used frequently to facilitate ransomware deployments and data theft, offline in a joint operation led by French and Dutch authorities. Investigators have seized 33 servers across 27 countries, confiscated all its related domains, and arrested the platform’s administrator.

Threat actors previously promoted the service on cybercrime forums as a “privacy-focused tool” that ignored police data requests. Authorities have now identified all users of the platform, sharing intelligence on 506 individuals to support ongoing global investigations into connected fraud schemes and ransomware attacks.

The Bad | New macOS Stealer Variant Masquerades as Apple, Google & Microsoft in Multi-Stage Attack

SentinelOne researchers have identified a new macOS infostealer variant using the build tag “Reaper”, the latest evolution within the SHub Stealer malware family.

The infection chain uses fake WeChat and Miro installers hosted on typosquatted domains to lure in victims. The websites employ extensive anti-analysis techniques, blocking developer tools and fingerprinting visitors to avoid virtual environments.

To sidestep Apple’s recent macOS Tahoe mitigations, the malware abandons traditional “ClickFix” social engineering in Terminal, instead leveraging the applescript:// URL scheme to launch the macOS Script Editor.

The malicious HTML from the webpage creates a script deliberately padded with ASCII art to hide the malicious command. On execution, the script displays a message indicating it is downloading an Apple security update.

HTML source code showing the construction of the malicious AppleScript
HTML source code showing the construction of the malicious AppleScript

Once executed, the AppleScript prompts the user for their password to access protected Keychain items and decrypt credentials. Reaper extensively harvests browser data, password manager extensions, and iCloud account details.

On top of this, the variant introduces an AMOS-style Filegrabber module that targets business and financial documents, dividing the stolen data into 70MB chunked ZIP archives for exfiltration.

The Reaper malware also actively hijacks desktop cryptocurrency applications by terminating the active processes and replacing the legitimate core app.asar file. To bypass macOS Gatekeeper, the script clears quarantine attributes and applies ad hoc code signing to the modified application bundle.

Reaper is an example of SHub operators extending beyond credential and wallet theft. Unlike earlier SHub builds, this variant establishes persistence by installing a persistent backdoor on the compromised machine.

Since the infection chain layers in spoofs of trusted software and big brand names, macOS defenders are reminded to watch for unplanned AppleScript activity, suspicious outbound traffic, and any unexpected creation of LaunchAgents and related files.

The Ugly | Two Microsoft Defender Zero-Days Allow SYSTEM Privileges & Trigger DoS States

Two Microsoft zero-days affecting its Defender antimalware suite are being actively exploited to trigger denial-of-service (DoS) states on unpatched Windows devices. The first flaw, tracked as CVE-2026-41091 (CVSS: 7.8), is a privilege escalation vulnerability impacting the Microsoft Malware Protection Engine versions 1.1.26030.3008 and earlier. This engine provides scanning, detection, and cleaning functions for Microsoft’s native security software. The vulnerability arises from an improper link resolution weakness before file access (‘link following’) in Defender, which attackers can leverage to successfully gain SYSTEM-level privileges on compromised machines.

The second vulnerability, tracked as CVE-2026-45498 (CVSS: 7.5), impacts the Microsoft Defender Antimalware Platform versions 4.18.26030.3011 and earlier. The platform underpins the suite of security tools used by Microsoft’s System Center Endpoint Protection, System Center 2012 R2 Endpoint Protection, System Center 2012 Endpoint Protection, and Security Essentials. If successfully exploited, this flaw allows threat actors to trigger DoS conditions on unpatched Windows devices.

Microsoft has since released updated versions for both the engine and platform to mitigate these issues. While the vendor notes that default configurations should automatically install these critical platform updates, administrators are strongly advised to manually verify whether Windows Defender Antimalware Platform updates and malware definitions are configured to verify and autoinstall the updates. According to its security advisory, users should check their Antimalware ClientVersion number in the Windows Security settings.

In response to active in-the-wild exploitation, CISA has added both flaws to its Known Exploited Vulnerabilities catalog and issued a mandate requiring Federal Civilian Executive Branch (FCEB) agencies to thoroughly secure their Windows servers and endpoints by June 3, 2026.



from SentinelOne https://ift.tt/7XuiTGw
via IFTTT

Tracking Iranian APT Screening Serpens’ 2026 Espionage Campaigns

Executive Summary

Unit 42 researchers have observed evidence of cyberattacks by the Iran-nexus advanced persistent threat (APT) group Screening Serpens (aka UNC1549, Smoke Sandstorm and Iranian Dream Job). Based on our visibility, we believe that the group targeted entities in the U.S., Israel and the United Arab Emirates, and likely two additional Middle Eastern entities.

This research follows an evolution through cyberattacks in mid-February through April 2026. The timing of these campaigns aligns closely with that of the regional conflict that started in the Middle East on Feb. 28, 2026. We discovered six new remote access Trojan (RAT) variants developed and deployed between February and April 2026.

Screening Serpens has been active since at least 2022. Their recent activity demonstrates an increase in technical capabilities and operational resilience.

Screening Serpens primarily targets technology sector professionals, using highly tailored social engineering. The group frequently uses personalized recruitment lures that impersonate trusted brands and hiring platforms, to trick targets into initiating the infection chain.

We assess with moderate-high confidence that the campaigns discussed in this article are conducted by Screening Serpens. The group has maintained a consistently high operational tempo throughout March and April 2026.

We have grouped the six newly discovered RAT variants into two new malware families that were deployed in concurrent espionage campaigns. Based on the timing of deployment, our analysis indicates two sets of coordinated cyberattacks. At least one variant was compiled and deployed with specific timing instructions.

Our analysis reveals a continuous cycle of development and deployment, characterized by specialized and upgraded variants with diverse functionalities, as shown in each targeted campaign.

The most critical evolution in the group’s recent campaign uses a technique called AppDomainManager hijacking. This hijack method manipulates the initialization phase of .NET applications to proactively disable the application’s own security mechanisms via a legitimate configuration file. The disabled security in these apps left the targeted entities vulnerable to the deployed multi-functional RATs.

Palo Alto Networks customers are better protected from the threats described in this article through the following products and services:

Cortex AgentiX Agentic Assistant can assist teams in investigating incidents.

If you think you might have been compromised or have an urgent matter, contact the Unit 42 Incident Response team.

Screening Serpens Overview

Screening Serpens is an Iran-nexus APT group operating as a cyberespionage group aligned with Iranian intelligence objectives. While historically focused on regional targets in the Middle East, the group gained industry attention in late 2025 when Check Point Research detailed its strategic expansion into Western Europe.

During these campaigns, Screening Serpens consistently set its sights on high-value sectors, heavily targeting aerospace, defense manufacturing and telecommunications organizations. These operations are characterized by targeted social engineering campaigns, using lures designed specifically to trick job seekers in these key sectors.

Between February and April 2026, we identified six new remote access Trojan (RAT) variants that Screening Serpens deployed during the recent regional conflict. Based on VirusTotal metadata, it appears these samples may have been used against targets across the U.S., Israel and the UAE as well as two additional Middle Eastern entities. The samples are split into two distinct malware families:

  • A newly discovered malware family that we call MiniUpdate
  • An evolved iteration of a malware family named MiniJunk that we track as MiniJunk V2

Both families build directly upon the actor's established playbook. Their infection chains begin with targeted spear phishing lures, leveraging DLL sideloading for execution. The threat actor routes command and control (C2) traffic through a set of three to five unique domains, mostly hosted by Azure, dedicated to each target and variant. This technique prevents cross contamination to increase operational resiliency.

Timeline of Recent Cyber Activity

Here is the timeline of events in the recent Screening Serpens campaign:

  • In late 2025, Screening Serpens expanded to targets in Western Europe.
  • In mid-February, 2026, we found an indication of a payload delivery to a Middle Eastern target.
  • In late March 2026, we identified samples uploaded to VirusTotal from organizations in the U.S. and Israel.
  • Additional samples from the UAE and another Middle Eastern entity were discovered in mid-April 2026.

Figure 1 shows the transition from campaign preparation to a surge in coordinated attacks following the onset of the regional conflict.

A timeline illustration in the shape of a serpent, depicting a sequence of cyber campaign events from February 2023 to late 2025. Key events include the start of a Middle Eastern phishing campaign, indications of Iranian conflict, and the introduction of malware, MiniJunk V2. MiniUpdate samples were uploaded throughout March in the U.S. and Israel, and in April from the UAE. The campaign is projected to expand globally by late 2025.
Figure 1. Timeline of Screening Serpens documented activity.

As seen in Figure 1, we observed the MiniUpdate family samples uploaded on March 26, April 15 and April 17. We observed the MiniJunk V2 family samples uploaded on Feb. 17 and in an upload on March 27.

We discuss the MiniUpdate family first in our analysis, and then cover the details of MiniJunk V2.

MiniUpdate RAT Analysis

After reading Check Point's initial report, we pivoted off the specific file name (Hiring Portal.zip) of another known Screening Serpens artifact. In doing so, we uncovered four samples that attackers deployed in two sets of coordinated attacks during the recent conflict. VirusTotal metadata indicates that the campaigns may have targeted entities in the U.S. and Israel on March 26, 2026, and most recently, the UAE and another Middle Eastern entity on April 15 and 17, 2026, respectively.

We named this malware family MiniUpdate, referencing the internal file name that we observed within these payloads: UpdateChecker.dll.

By comparing the two sets of coordinated attacks, we observed continued refinement of the malware’s abilities over the course of a month. The differences we identified between the samples were superficial changes to things like opcode mappings and specific functionalities, such as the latest variant’s ability to exfiltrate files in chunks. The most significant difference between the malware variants is the rotation of their C2 domains. While we observed these active adjustments, we did not observe a significant evolution in the malware itself.

MiniUpdate: March U.S. Campaign

Attackers delivered this variant via an archive file, as part of a campaign impersonating a global air carrier. Deployment of this malware began no earlier than March 26, 2026.

Initial Delivery and Targeted Recruitment Lures

An analysis of the archive's contents reveals a tailored social engineering trap aimed specifically at technical personnel. The ZIP contains a nested payload archive (Hiring Portal.zip) packaged alongside six PDF documents.

These PDFs are crafted job requisitions targeting high-level IT and engineering roles (e.g., Senior Software Engineer Job ID JR205894.pdf). Attackers mimicked legitimate corporate job applications by including specific job IDs, increasing the likelihood that the target will review the descriptions and extract the nested Hiring Portal.zip.

Targets likely believed they were accessing an application portal or a technical assessment. We did not find any indication in this campaign of a breach into the global air carrier’s infrastructure. The impersonation was limited to using its name and branding.

Figure 2 shows all the falsified job documents and the Hiring Portal.zip archive.

A screenshot of a file directory displaying five PDF files and one ZIP file, each related to job roles such as Data Analytics & Business Intelligence Specialist, IT Project & Applications Manager, and Senior Software Engineer. The files have modification dates in March 2023 and range in size from 116 KB to 248 KB.
Figure 2. Contents of the archive.

Figure 3 shows one of the Senior Software Engineer Job ID JR205894.pdf files from this archive, which contains detailed job requirements.

A screenshot of a job description for a Senior Software Engineer at a technology and innovation team. The role involves software solutions for businesses, collaboration with cross-functional teams, and process optimization. Key responsibilities include developing applications, maintaining software, supporting integration, resolving issues, and developing technology strategies. The position mentions collaboration, cloud-based systems, and database management.
Figure 3. A fake job description document, designed by the attacker to impersonate a global air carrier company.

Figure 4 shows the contents of the Hiring Portal.zip archive contained in the initial archive file.

A screenshot of a file explorer window displaying the contents of "Hiring Portal.zip." It includes six files, with size, compressed size, and date modified listed.
Figure 4. Contents of Hiring Portal.zip.

Upon executing setup.exe, the malware triggers a spoofed error window titled Hiring Portal.zip to establish legitimacy with the target, as Figure 5 shows.

Error message from Hiring Portal: 'Couldn't connect to survey server' with an OK button.
Figure 5. Spoofed Hiring Portal error window.

MiniUpdate: March Israel Campaign

This variant was delivered via an archive file, to impersonate an install file for a popular video conferencing platform. Our analysis reveals that this variant was recently deployed, no earlier than March 26, 2026, ostensibly against an Israeli entity.

Social Engineering and Initial Access

Analysis of sequential artifact uploads to VirusTotal from March 2026 provides a view into Screening Serpens’ social engineering tactics. The threat actor actively engaged with the target to deliver convincing lures. By correlating the timeline of these uploads, we can map the sequence of the attack:

  • Establishing trust: The target received a number of authentic video conferencing links, possibly to build trust during the phishing campaign.
  • Initial lure: Capitalizing on the precedent of legitimate links, the attacker delivered a lookalike domain to attempt to compromise the target: hxxps[:]//[redacted][.]live/meeting/edcdba624ddb43c2a1dcf334aa493068

Looking into the response reveals a phishing landing page designed to mimic an authentic meeting invitation. It uses the brand’s familiar styling and contains a "join from workplace app" button. The goal of this cloned frontend design is to trick a target into believing they need to install or update their client software to enter a scheduled meeting.

However, the page contains a payload, hidden within JavaScript code, which redirects the victim’s download request away from the legitimate servers. If the victim interacts with the page, a payload delivery is triggered from a third-party file-sharing service via the following URL: hxxps[:]//2117.filemail[.]com/api/file/get?filekey=T0EnWQ6NugHkW_kLfDxPBEw_um6NSkg9ZwNRQ_5lrKrLLUo35pV8m3TKv1LqF3zZzdUm

  • Payload delivery: The targeted lure tricked the victim into downloading the malicious archive from the impersonating website. This file served as the delivery archive for a malicious sideloading chain.
    There is no indication that the attackers compromised or breached the impersonated organization’s infrastructure or systems. Their brand was only used in the context of impersonation to compel the victim to manually execute the malicious payload.

Figure 6 shows the contents of that archive. The first six files are part of the execution chain, while the last file is a genuine installer for the video conferencing application.

A screenshot of a file directory showing various files and a hidden file beginning with an underscore. File types include Application, Configuration Source, Microsoft Edge HTML, and Application extension, with dates from 2025 and 2026.
Figure 6. Contents of the zip archive.

MiniUpdate: Mid-April Middle Eastern Campaigns

In the attacks that may have targeted entities in the UAE and potentially another Middle Eastern country, we identified two new MiniUpdate variants, compiled and submitted to VirusTotal between April 15 and April 17, 2026. While the initial loading mechanism remains consistent with previous variants, leveraging the same impersonation decoy, this version introduces a few upgrades to its infrastructure and core capabilities.

In the variant that may have targeted an entity in the UAE on April 15, 2026, the threat actor rotated the C2 domains to impersonate a health sector entity, using:

  • PremierHealthAdvisory[.]com
  • PremierHealthAdvisory.azurewebsites[.]net
  • Premier-HealthAdvisory.azurewebsites[.]net

In the variant that may have targeted another Middle Eastern entity on April 17, 2026, the threat actor rotated the C2 domains to impersonate a financial sector entity, using:

  • Ramiltonsfinance[.]com
  • Ramiltonsfinance.azurewebsites[.]net
  • Ramiltons-finance.azurewebsites[.]net

Furthermore, the April variants feature an expanded command dispatcher with 18 distinct opcodes, two more than the earlier March campaigns. This increase is valuable to the attackers because it expands their toolkit for stealing data. The primary new command allows the malware to break large files into smaller chunks during upload, providing a stealthier and more reliable way to exfiltrate data from compromised environments.

MiniUpdate Loading Flow

Advanced AppDomainManager Hijacking: Native EDR Evasion

The threat actor employed a .NET-specific code execution technique known as AppDomainManager hijacking. This method allows the attackers to hijack the execution flow of a legitimate application by manipulating its configuration file, granting them arbitrary code execution before the host application even starts. Consequently, the malware can preemptively disable logging mechanisms and other core features that endpoint security tools rely on to detect and block malicious activity.

At its core, this configuration relies on the <probing privatePath="."/> tag to force the local sideloading of an attacker-controlled assembly. It then instantiates a custom AppDomainManager type (such as MyAppDomainManager) to achieve this Pre-Main() execution.

However, the true sophistication of this variant lies in its native defense evasion directives. By adding just a few specific lines of XML, the threat actor instructs the .NET common language runtime (CLR) to proactively disable its own security mechanisms:

  • Silencing event tracing for Windows: The configuration includes the directive <etwEnable enabled="false"/>. Event Tracing for Windows (ETW) is the primary telemetry source used by modern endpoint detection and response (EDR) solutions to monitor .NET execution, track loaded assemblies and detect malicious behaviors in memory. By disabling ETW natively via the application configuration, the attacker potentially shrouds the EDR to the CLR's runtime behavior without needing to perform suspicious memory patching or API hooking.
  • Bypassing signature validation: The <bypassTrustedAppStrongNames enabled="true"/> directive instructs the CLR to skip strong-name signature validation. This ensures that even if the system normally requires cryptographic verification for loaded assemblies, the attacker's unsigned or tampered InitInstall.dll will load silently without throwing a security exception.
  • Preventing safe redirections: The XML configuration file includes <publisherPolicy apply="no"/>. Publisher policies are typically used by Microsoft to redirect application bindings to newer, safer or patched versions of an assembly. Disabling this default policy ensures that the CLR loads the attacker's localized payload and ignores any system-level overrides.
  • Forced runtime environment (safe mode): The configuration uses the <requiredRuntime safemode="true" imageVersion="v4.0.30319"/> directive. This parameter ensures the application executes in a highly controlled, predictable environment by requiring the exact specified version of the .NET runtime. By forcing this strict environment, the attacker reduces the risk of accidental application crashes, which would generate Windows error pop-ups and logs, immediately alerting the user or defenders that something is wrong.

Figure 7 shows the full XML configuration.

A screenshot of a configuration file with XML code. Important sections are highlighted, including safemode set to true, the useLegacyV2RuntimeActivationPolicy set to "no," and bypassTrustedAppStrongNames set to true.
Figure 7. Contents of setup.exe.config.

This represents a mature living-off-the-land approach to execution. Rather than writing complex shellcode to unhook security monitors or patch ETW in memory, actions that often trigger behavioral alerts, Screening Serpens asks the .NET runtime to turn off its own security mechanisms via a legitimate configuration file. Combined with the Pre-Main() execution timing, the malicious InitInstall.dll payload runs in an entirely unmonitored, highly privileged context.

Stage 1: Installation and Creating Persistence

When the advanced .config file successfully hijacks the CLR initialization, it triggers the execution of InitInstall.dll. This C# assembly acts as the primary loader and installer for the second malware family, MiniUpdate.

Before staging the final payloads, the malware unpacks its configuration. The malware's static constructor uses a custom, two-step cipher to decrypt nine key configuration strings. First, the constructor reverses the input bytes interpreted as UTF-8. Next, it applies a standard ROT13 cipher to the alphabetic characters.

Once the strings are decrypted, the loader initiates a sequence that blends user interface (UI) deception with stealthy file staging and persistence.

1. The decoy UI and lure: To disguise the malicious activity happening in the background, the loader launches a background thread that renders a borderless, transparent window. This window displays a custom circular loading spinner specifically designed to mimic a legitimate installer progress indicator. This window has no taskbar entry, making it difficult for a user to inspect or close, as Figure 8 shows.

A spinning blue loading icon on a white square background.
Figure 8. An interface window mimicking a legitimate installer.

2. Staging the MiniUpdate payload: While the fake spinner is displayed, the malware resolves its current directory and constructs a new hidden installation path under the legitimate local appdata directory of the video conferencing application’s folder.

The malware specifically adds a \bin\update folder to hold its files. If the directory does not exist, the malware creates it. The malware then copies and renames four files from the initial infection folder into this new directory:

  • setup.exe is renamed to update.exe
  • UpdateConfig.xml is renamed to update.exe.config
  • Updater.dll is copied as is
  • UpdateChecker.dll (the MiniUpdate payload) is copied as is

3. Establishing persistence: With the files staged, InitInstall.dll leverages Windows Task Scheduler to ensure the payload survives reboots. It creates a scheduled task that is configured to trigger every day at 09:30 local time.

Figure 9 shows the newly created scheduled task in a controlled test environment.

A screenshot of the Task Scheduler interface displays a task running daily at 8:30 AM. The task is set to start a program. The interface includes tabs: General, Triggers, Actions, Conditions, Settings, and History (disabled).
Figure 9. Task Scheduler window showing the associated scheduled task.

After a final 30-second delay, the loader forces the scheduled task to run immediately, starting the execution of Stage 2 by running update.exe.

Stage 2: Anti-Analysis Checks

When the scheduled task triggers the renamed setup binary (update.exe), the malware initiates a second AppDomainManager hijack to safely transition to the next stage. The threat actor uses the dropped update.exe.config file to reapply their native evasion directives, explicitly disabling ETW and strong name verification. This effectively hollows out the legitimate Microsoft process, allowing the next payload, Updater.dll, to load into an unmonitored memory space.

Operating entirely within this blinded environment, Updater.dll acts as a gatekeeper. Before deploying the core RAT, it ensures the malware is executing within the intended infection chain by performing two strict environmental checks:

  • Process verification: The DLL verifies that the current running process is named update.exe.
  • Sandbox evasion: It checks if the parent process is svchost.exe. Because the malware relies on a scheduled task to launch, svchost is the natural parent. If a security analyst or automated sandbox executes the file directly, this check will fail and the malware will silently terminate.

Once the environment is validated, the loader dynamically constructs the path to the final UpdateChecker.dll payload. It loads the module into memory and invokes the CheckForUpdates export, officially handing over control to the MiniUpdate RAT.

Figure 10 shows the full flow of this MiniUpdate malware.

A flowchart illustrating the MiniUpdate malware process. It includes multiple steps, such as creating processes, dropping files, and executing tasks. Key components involve the initial host NET application, primary loader, and the execution of Appdomain Hijack. The diagram details the connection to C2 and commands, ending with the malware's silent termination.
Figure 10. MiniUpdate malware flow.

Stage 3: Payload Execution and Core Functionality

The MiniUpdate payload operates via external C2s and a compromised digital signature. This variant is driven by a 16-opcode dispatcher, giving attackers extensive control over file operations, shell execution and process manipulation.

C2 Architecture and Network Execution

This variant is designed to cycle through three different command servers in a specific order, checking each one for instructions:

  • buisness-centeral.azurewebsites[.]net
  • buisness-centeral-transportation.azurewebsites[.]net
  • Buisness-centeral-transportation[.]com

The following user agent is used in the communication:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36

  • Digital signature misuse: This payload is digitally signed under the name of a software company whose signature appears to have been stolen or impersonated.
  • Operations security (OPSEC) shift (plaintext strings): MiniUpdate stores all API names, C2 domains and endpoints in plaintext within the .rdata section. This lack of string obfuscation suggests either a rushed deployment cycle or the involvement of a different development cell within the threat group. Conversely, the MiniJunk V2 samples featured heavy Mixed Boolean-Arithmetic and XOR obfuscation.
Core Capabilities

The analyzed payload functions as a highly versatile backdoor, granting the attacker near-complete operational control over the compromised host's file system, processes and environment. Command polling occurs via GET requests to the /agent/poll endpoint. The internal command dispatcher processes a Base64-decoded binary format and supports 16 distinct opcodes. Key capabilities include:

  • Arbitrary command execution: Executes shell commands via cmd.exe /c
  • Dynamic code execution: Loads arbitrary DLLs directly into memory to run specific exported functions
  • Process manipulation: Enumerates running processes and terminates them
  • Data exfiltration: Uploads files to the C2 server, including support for chunked uploads
  • Privilege escalation: Requests User Account Control (UAC) elevation
  • Persistence: Creates a logon-triggered scheduled task named WindowsSecurityUpdate, with built-in capabilities to remove or reinstall this task

MiniJunk V2 Analysis

We assess the second malware family identified in this campaign, MiniJunk V2, is an evolved version of the previously documented MiniJunk malware, featuring updated core functionalities. We correlate this malware family to Screening Serpens, based on the setup.exe file in the lure archive. As documented in Check Point reporting, the threat actor uses this exact legitimate binary to sideload their malicious payloads. Furthermore, we observed the same defense evasion tactics that Check Point's research outlined. Across all samples, the threat actor uses junk code and padding to artificially inflate the file size, successfully bypassing endpoint detection and scanning limits.

On Feb. 17, 2026, a MiniJunk V2 sample appearing to target an entity in the Middle East surfaced shortly before the regional conflict. Our visibility indicated another campaign on March 27, 2026, that may have targeted an entity in the U.S. one month after the conflict began. This timeline strengthens our assessment that the payload is a recently upgraded version derived from previously documented campaigns, illustrating a continuous cycle of development and deployment.

MiniJunk V2: February Middle Eastern Campaign

On Feb. 17, 2026, we identified evidence of a spear-phishing campaign targeting a professional working in the technology sector, based in a Middle Eastern country. Our analysis of the files in the malicious archive indicates that the preparation for this campaign and its malware development began in late 2025. The threat actor conducted careful reconnaissance, exploiting the target's active job-hunting footprint to engineer a customized lure. To establish legitimacy and coerce the target to execute their payload, the attackers shared a spoofed recruitment URL from a legitimate, well-known employment website.

Social Engineering and Initial Access

The threat actor initiated the attack by distributing a spoofed recruitment URL: hxxps[:]//[REDACTED][.]com/career/recreuitment/[REDACTED]. This endpoint currently returns an HTTP 404 Not Found status code, which we assess was a visual decoy intended to mislead the target.

The URL’s specific misspelling (recreuitment) indicates an intentional, fraudulent fabrication, engineered with the knowledge that the link would remain non-functional by design. Analysis shows no indication that the impersonated organization’s infrastructure, systems or domains were compromised or breached.

The group likely used this non-functional URL to prompt the target to take a work around solution into an offline portal. The target would then be redirected to a dedicated storage instance hosted within an attacker-managed ONLYOFFICE workspace. This infrastructure served as the delivery point for the primary payload, where the victim was induced to download a malicious archive disguised as legitimate recruitment materials

The attack execution advances when the victim complies with the lure instructions, manually retrieving and downloading the weaponized Portal.zip archive. This archive contains a file named Setup.exe and three hidden files. Since the default Windows settings do not reveal hidden files, a user would not normally see these three files. Figure 11 shows the contents of the archive.

A screenshot showing file explorer windows with a "Portal.zip" folder. The folder contains files including "Setup.exe." Another window displays "Folder Options," highlighting the default Windows setting to hide certain files. A label points to hidden files loaded by "Setup.exe".
Figure 11. Contents of the Portal.zip archive containing hidden files, with uevmonitor.dll used as the payload for the attack.

One of the hidden files is a malicious DLL named uevmonitor.dll that contains the payload for this attack. If a user runs the Setup.exe file, the action initiates an infection chain under the context of the logged-in user.

AppDomainManager: Sideloading and Hijacking

During our analysis of the MiniJunk V2 sample, we observed the threat actor using an older version of .config file to facilitate local sideloading. In this instance, the attackers authored a custom malicious DLL named uevmonitor and deployed it alongside a legitimate .NET executable. To successfully sideload their payload into the host process, they used the <probing privatePath="./"/> directive, forcing the application to prioritize its local working directory, which is a key prerequisite for DLL sideloading.

The original MiniJunk configuration lacked operational security measures such as evasion features, making it susceptible to detection. The attackers updated their newer tool, MiniUpdate, with stealthy evasion techniques. Figure 12 shows the original .config file, which was used only for sideloading the uevmonitor.dll file.

A screenshot of a code snippet showing XML configuration settings.
Figure 12. Contents of the .config file.

Technical Analysis of the Payload

Serving as the primary loader, the uevmonitor.dll assembly initiates the infection chain once executed by the initial, legitimate Setup.exe host process. It silently drops two embedded payloads into the local AppData directory:

  • SoftwareLicencing.exe: a renamed, legitimate Microsoft setup binary
  • unbcl.dll: the core malicious payload

To maintain its foothold, the loader creates a scheduled task for persistence named Synchronize OS and simultaneously displays a decoy system error to the user to mask this background activity. The sequence culminates when the scheduled task triggers SoftwareLicencing.exe, which specifically sideloads the malicious unbcl.dll into its trusted memory space. This action successfully deploys the heavily obfuscated RAT, granting the attacker operational control via externally-hosted C2 infrastructure.

Figure 13 demonstrates the entire flow to deploy the malicious RAT, including AppDomainManager hijacking and two DLL sideloading instances.

A flowchart depicting the process of the MiniJunk V2 malware. It includes stages such as displaying a decoy error message, saving payloads to a scheduled task location, dropping various files, and executing the final payload. Key files mentioned include malicious archive, legitimate setup.exe and various DLLs.
Figure 13. MiniJunk V2 malware flow.

C2 Loop and Network Execution

During execution, the malware dynamically decrypts data within its code to retrieve five C2 domains:

  • licencemanagers.azurewebsites[.]net
  • LicenceSupporting.azurewebsites[.]net
  • PeerDistSvcManagers.azurewebsites[.]net
  • ThemesManagers.azurewebsites[.]net
  • ThemesProviderManagers.azurewebsites[.]net

These domains mimic legitimate Windows service names, attempting to blend in with network communication.

Simultaneously, the malware uses Mixed Boolean-Arithmetic decryption to construct a hard-coded User-Agent string. The resulting string mimics legitimate Microsoft Edge browser traffic:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0

Behavioral analysis confirms that the malware interacts with specific API endpoints on the C2 servers. The endpoints implemented are:

  • /api/app/check: The initial beacon, handling victim registration and establishing the session.
  • /api/app/update: Retrieves execution commands and downloads subsequent payloads.
  • /api/app/comment: Exfiltrates data and sends operational status reports to the threat actor.

The malware’s .rdata section is packed with thousands of junk strings, including Java and Python tracebacks, SQL queries and .NET exceptions. These strings repeat every 0x1E50 bytes. This repetition serves two purposes:

  • Flooding string extraction tools with irrelevant data
  • Inflating the binary size to around 12 MB in an attempt to bypass file-size limits on certain automated sandboxes

The sideloading chain and malicious executable triggered Cortex XDR to flag this threat as high risk. It also prevented the threat from executing before any user interaction could take place. Figure 14 shows this detection and prevention.

A screenshot of Cortex XDR report interface. The summary details a DLL Hijacking issue identified, involving WildFire Malware detection and a suspicious DLL. Two issues are listed: "WildFire Malware" with one alert for a suspicious DLL detected, and "DLL Hijacking" also with one alert. The event details section provides specific information, such as time, status, and path. The report also features a graph and information to the right side.
Figure 14. The infection chain originating in malicious DLL sideloading (categorized "DLL Hijacking"), as seen, detected and prevented by Cortex XDR.

MiniJunk V2: March U.S. Campaign

While tracking the unique SoftwareLicencing.exe file, we discovered a newly developed malware variant that may have been deployed against a U.S.-based target. First submitted to VirusTotal on March 27, 2026, the malware is delivered within an archive named Portable platform.zip. This malware sample appears to have been actively developed and used during the recent regional conflict.

This latest iteration features a complex, multi-stage execution chain designed to evade detection. It relies on a social-engineering decoy graphical user interface (GUI) to deceive the target while quietly establishing a heavily obfuscated C2 connection.

Social Engineering and Initial Access

The infection begins with the Portable platform.zip lure archive, hosted on a unique ONLYOFFICE DocSpace: hxxps[:]//docspace-y4cumb.onlyoffice[.]com/storage/files/root/folder_3602000/file_3601577/v1/content.zip[...]

Figure 15 shows the archive content.

A screenshot of a Windows File Explorer window showing a folder named "Portable Platform." It contains three items with their respective modification dates.
Figure 15. Contents of Portable Platform.zip.

Figure 16 shows the file folder content.

Screenshot of a file explorer window displaying two files, with their names, modification date and time and sizes listed. Navigation and sorting options are visible at the top.
Figure 16. Contents of file folder inside Portable Platform.zip.

Upon extraction, the archive initiates a DLL sideloading sequence. The execution flow leverages the legitimate Setup.exe, which subsequently loads two malicious components:

  • Unbcl.dll: a social-engineering decoy
  • Connection.dll: the primary payload, a RAT

The execution of Unbcl.dll creates a background thread displaying a GUI to the target. The window is titled “Meeting Room” and prompts the victim to provide a “Meeting Room URL.” This provides a plausible reason for the execution, tricking the victim into believing they are joining a legitimate web conference while the primary C2 beacon operates silently in the background.

Figure 17 shows the decoy window.

A screenshot of a software window titled "Meeting Room." It contains a text box labeled "Meeting Room URL" and a "Send" button.
Figure 17. A meeting room decoy window.

When the Connection.dll RAT runs, it follows a strict execution sequence:

  1. It performs a hard-coded date-based validity check to ensure that the RAT runs on any date that is after March 27, 2026, 13:30:00 UTC. This validity check serves as an execution trigger that potentially enables the threat actor to avoid sandbox analysis, bypass initial security screenings and maintain a low profile until the predetermined operational phase begins.
  2. If successful, the RAT spawns the main worker thread, constructs a file path using its internal name (SystemtUpdateTaskMachine.exe) and performs an instance check to ensure it is only running once.

Technical Analysis of the Payload

The Connection.dll payload is another RAT with multiple capabilities and defense evasion mechanisms.

Once in the main loop, the malware XOR-decrypts (using a single-byte key, 0x8A) data within its code to acquire a Chrome-based User-Agent string and three URLs using Azure-hosted C2 domains. These domains impersonate global companies operating within the technology, cybersecurity and artificial intelligence sectors:

  • hxxps[:]//NanoMatrix.azurewebsites[.]net
  • hxxps[:]//QuantumWeave.azurewebsites[.]net
  • hxxps[:]//ElementShift.azurewebsites[.]net

The malware beacons to the primary C2 base URL via an HTTP POST request. Depending on the parsed response, the malware will execute chunked uploads or downloads via specific transfer URLs or create additional threads for command execution.

Conclusion

The continuous tracking of the Iran-nexus APT group, Screening Serpens, reveals a persistent threat group that has remained active in recent months. The group has increased its operations since the regional conflict that started in February 2026, deploying two families of RAT variants across entities in up to five different countries.

A defining characteristic of these recent campaigns is the deep personalization of the attackers' lures. By leveraging tailored social engineering tactics, including fake job requisitions and spoofed video conferencing meeting invitations, the attackers lure victims into initiating the infection chain, thereby exposing their organizations to further exploitation.

We observed a significant evolution in the group’s tradecraft: For the first time, Screening Serpens has fused its standard DLL sideloading techniques with advanced AppDomainManager hijacking. By weaponizing the .NET initialization process and manipulating legitimate configuration files, the group can now preemptively bypass traditional security telemetry and execute payloads before most standard endpoint defenses are fully initialized. This tactic effectively allows attackers to establish persistence and maintain full operational control over the exfiltration of sensitive data.

Instead of relying solely on known malware indicators, defenders should ensure that EDR tools are fine-tuned to detect DLL sideloading and AppDomainManager hijacking. Treating these specific execution techniques as high risk will help organizations to identify behavioral anomalies associated with trusted, signed binaries loading untrusted modules.

As of April 2026, Screening Serpens activity shows no signs of slowing down and has continued to orchestrate sustained, adaptive global cyber campaigns. Organizations may expect further attempts in the near term and should harden their defensive posture to prepare for potential compromise attempts.

By leveraging its cutting-edge ecosystem, Palo Alto Networks customers are better protected from the threats discussed above through these industry-leading products:

  • The Advanced WildFire machine-learning models and analysis techniques have been updated to protect against the indicators shared in this research. Advanced WildFire is powered by Precision AI.
  • Advanced URL Filtering and Advanced DNS Security identify and block known domains and URLs associated with this activity in real time.
  • Cortex XDR and XSIAM help to prevent the threats described in this article, by employing the Malware Prevention Engine. This approach combines several layers of protection, including Advanced WildFire, Behavioral Threat Protection and the Local Analysis module, to prevent both known and unknown malware from causing harm to endpoints — all in a single interface.
  • Cortex Cloud customers are better protected against operations that target cloud environments through the proper placement of Cortex Cloud XDR endpoint agent and serverless agents. Screening Serpens’ use of cloud infrastructure to host command and control endpoints points to cloud architecture functionality. Cortex Cloud is designed to protect a cloud’s posture and runtime operations against the threats outlined here. It also helps detect and prevent malicious operations, configuration alterations and exploitation within cloud environments.
  • Cortex AgentiX Agentic Assistant streamlined our investigation by enabling the team to query the data using natural language, providing deeper context and insights, and suggesting clear recommendations on what we should do next. Figure 18 shows the AgentiX interface when querying for malicious activity in a tenant.
A screenshot of AngentiX report t detailing an 'Endpoint Investigation' about DLL hijacking events. It includes sections on issue details, key findings and insights, a case overview, and a request for further action. The report mentions techniques like DLL Sideloading and TRAPS (Threat Response Automation Service).
Figure 18. Querying for malicious activity in the tenant, using AgentiX.

If you think you may have been compromised or have an urgent matter, get in touch with the Unit 42 Incident Response team or call:

  • North America: Toll Free: +1 (866) 486-4842 (866.4.UNIT42)
  • UK: +44.20.3743.3660
  • Europe and Middle East: +31.20.299.3130
  • Asia: +65.6983.8730
  • Japan: +81.50.1790.0200
  • Australia: +61.2.4062.7950
  • India: 000 800 050 45107
  • South Korea: +82.080.467.8774

Palo Alto Networks has shared these findings with our fellow Cyber Threat Alliance (CTA) members. CTA members use this intelligence to rapidly deploy protections to their customers and to systematically disrupt malicious cyber actors. Learn more about the Cyber Threat Alliance.

Indicators of Compromise

Domains:

  • licencemanagers.azurewebsites[.]net
  • LicenceSupporting.azurewebsites[.]net
  • PeerDistSvcManagers.azurewebsites[.]net
  • ThemesManagers.azurewebsites[.]net
  • ThemesProviderManagers.azurewebsites[.]net
  • docspace-y4cumb.onlyoffice[.]com
  • NanoMatrix.azurewebsites[.]net
  • QuantumWeave.azurewebsites[.]net
  • ElementShift.azurewebsites[.]net
  • business-startup[.]org
  • business-startup.azurewebsites[.]net
  • Businessstartup.azurewebsites[.]net
  • app[redacted][.]live
  • buisness-centeral.azurewebsites[.]net
  • buisness-centeral-transportation.azurewebsites[.]net
  • Buisness-centeral-transportation[.]com
  • docspace-twpf0e.onlyoffice[.]com
  • PremierHealthAdvisory[.]com
  • PremierHealthAdvisory.azurewebsites[.]net
  • Premier-HealthAdvisory.azurewebsites[.]net
  • Ramiltonsfinance[.]com
  • Ramiltonsfinance.azurewebsites[.]neti
  • Ramiltons-finance.azurewebsites[.]net

URLs:

  • hxxps[:]//docspace-y4cumb.onlyoffice[.]com/storage/files/root/folder_3602000/file_3601577/v1/content.zip[...]
  • hxxps[:]//app[redacted][.]live/meeting/edcdba624ddb43c2a1dcf334aa493068
  • hxxps[:]//docspace-twpf0e.onlyoffice[.]com/storage/files/root/folder_3765000/file_3764519/v1/content.zip?filename=remote.[REDACTED].zip
  • hxxps[:]//2117.filemail[.]com/api/file/get?filekey=T0EnWQ6NugHkW_kLfDxPBEw_um6NSkg9ZwNRQ_5lrKrLLUo35pV8m3TKv1LqF3zZzdUm

SHA256 Hashes:

MiniUpdate: US Campaign

  • 44f4f7aca7f1d9bfdaf7b3736934cbe19f851a707662f8f0b0c49b383e054250 - Initial archive file
  • 332ba2f0297dfb1599adecc3e9067893e7cf243aa23aedce4906a4c480574c17 - Hiring Portal.zip
  • 0db36a04d304ad96f9e6f97b531934594cd95a5cea9ff2c9af249201089dc864 - UpdateChecker.dll

MiniUpdate: Israel Campaign

  • 38bd137c672bd58d08c4f0502f993a6561e2c3411773d1ae57ee0151a0a9d11d - Initial archive file
  • d4a7e9f107fe40c1a5d0139c6c6e25bf6bf57f61feff090bee28f476bb3cc3c2 - UpdateChecker.dll

MiniUpdate: UAE Campaign

  • bc3b44154518c5794ce639108e7b9c5fecb0c189607a26de1aaed518d890c7ad - UpdateChecker.dll

MiniUpdate: Middle Eastern Campaign

  • 74882085db2088356ed7f72f01e0404a0a98cda88ef56fb15ce74c1f36b26d27
  • bc3b44154518c5794ce639108e7b9c5fecb0c189607a26de1aaed518d890c7ad - UpdateChecker.dll

MiniJunk V2: Middle Eastern Campaign

  • 9cf029daca89523d917dafed0568d11d00e45ec96b5b90b4a1f7fd4018c7da84 - uevmonitor.dll
  • B19e06da580cf91691eda066ac9ee4b09c6e5dc26c367af12660fe1f9306eec4 - unbcl.dll

MiniJunk V2: U.S. Campaign

  • 8808c794c24367438f183e4be941876f1d3ecd0c8d2eb43b10d2380841d2283b - Portable Platform.zip
  • 43dc62cef52ebdd69e79f10015b3e13890f26c058325c0ff139c70f8d8eadcfa - Connection.dll
  • 9e4a658e6d831c9e9bdfe11884a75b7c64812ed0a80e8495ddf6b316505acac1 - unbcl.dll


from Unit 42 https://ift.tt/1ynmH8h
via IFTTT

Making Vulnerable Drivers Exploitable Without Hardware - The BYOVD Perspective

1 Introduction

This article provides a technical analysis of how many Windows kernel mode drivers can be interacted with from user mode without the hardware they were developed for. This work was motivated by driver-oriented vulnerability research and the need to evaluate the exploitability of individual findings, which frequently affect code whose reachability is hardware-gated. The methodology presented here should help anyone determine whether a particular Windows kernel mode driver vulnerability remains reachable - and thus potentially exploitable - even in the absence of the hardware the driver was developed for.

The reader is expected to have basic Windows driver knowledge, especially regarding device objects. The rest of this article is written with the assumption that the reader is already familiar with the concepts described in the introduction article: Anatomy of Access: Windows Device Objects from a Security Perspective.

Just like the introduction article, this resource is not focused on any specific bug class, but rather the attack surface and, to an extent, the Windows Plug and Play architecture.

All the tests demonstrated here were conducted on Windows 11 23H2 (winver 10.0.22631.3007).

For more such latest threat research and vulnerability advisory, please subscribe to Atos Cyber Shield blogs.

2 The offensive value of kernel mode drivers

In addition to the obvious Local Privilege Escalation potential, vulnerable drivers are often abused in BYOVD attacks - a post-exploitation technique leveraged by attackers to disrupt system defenses such as EDR components.

Two main criteria determine whether a driver vulnerability is a strong candidate for BYOVD attacks: 1. Exploitation allows meaningful disruption of an otherwise tamper-resistant security component. Examples include kernel-level vulnerabilities granting arbitrary memory read/write access, arbitrary code execution, or arbitrary resource abuse (e.g., overwriting files, closing handles, or terminating processes). 2. Its exploitability is independent of rare system conditions, such as the presence of specific hardware.

Although BYOVD-style attacks have been well documented for years, with numerous public reports and research papers on the topic (e.g. https://www.ndss-symposium.org/wp-content/uploads/2026-s1491-paper.pdf, https://blackpointcyber.com/blog/qilin-ransomware-and-the-hidden-dangers-of-byovd/, https://www.sophos.com/en-us/blog/itll-be-back-attackers-still-abusing-terminator-tool-and-variants), none of them specifically examines the role of hardware-gating in driver vulnerability reachability.

3 Device object creation and maintenance - common patterns

The analysis provided in this resource is structured around device objects, because they are the most viable attack vector. However, the techniques demonstrated here practically impact driver code reachability from userland in general, not just via IRP.

The most common obstacles in attacking a driver via its device object are: 1. The device object is not created. 2. The driver's internal state does not allow the exercise of the vulnerable behavior despite the device object being accessible.

Both scenarios are very common when dealing with a device driver deployed on a system without the corresponding physical hardware.

In the rest of the article I am often referring to device stacks and device nodes. I have covered device stacks quite broadly in my introduction article. While a device node and a device stack are not the same thing, the terms are often used interchangeably, because every device node has exactly one device stack.

3.1 Unconditional creation upon driver load

Many drivers, especially non-PnP drivers, create their device objects either directly from within their DriverEntry function, or from some other function that gets invoked in the direct call chain originating from DriverEntry.

Multidev_WDM demo driver exemplifies this pattern. We can see the device creation invoked right away in DriverEntry:

CDO creation invoked directly from DriverEntry

The driver also removes the device object by calling IoDeleteDevice, but that happens only when DriverUnload is called (when the driver is being unloaded):

CDO cleanup from DriverUnload

Drivers built this way can be interacted with after simple deployment consisting of just two steps:

  1. Create the driver's service entry: sc.exe create SampleDrv type= kernel start= demand binPath= System32\drivers\SampleDrv.sys

sc.exe create SampleDrv type= kernel start= demand binPath= System32\drivers\SampleDrv.sys

  • Start the service (driver will load): sc.exe start SampleDrv

If we look at a randomly picked driver from https://loldrivers.io/, we will see that its deployment command matches this pattern:

LOL drivers - zam64.sys deployment

But most device drivers do not fall into this category, as we will see in the following sections of this article.

3.2 Conditional device creation and maintenance

Oftentimes driver initialization routines perform additional checks. For example, kernel mode components of security software (EDR, anti-virus, monitoring, enhanced authentication etc.) tend to check for product-specific registry keys and entries, which are created and initialized during normal product deployment.

Actual device drivers (created to drive physical hardware) tend to only create their device objects in the presence of that hardware. Without it they either: - do not attempt to create any device objects at all, - they remove any device objects shortly after their creation, by calling IoDeleteDevice.

Let's focus on how that logic is implemented and evaluate whether and how it can be worked around, especially from the BYOVD perspective - by solely operating from userland (with no physical/hypervisor access).

By the way, the second scenario, in which a device object is first created and then deleted shortly after, creates a situation that could be considered a race condition, because there is a short time window in which the device object exists.

3.3 PnP-specific callbacks as the main location of PnP driver initialization logic

In PnP-compatible drivers (which make up most of device drivers), initialization logic extends beyond DriverEntry into the following PnP-specific routines: AddDevice and the IRP_MJ_PNP handler.

This section explores both of them and explains why most PnP-compatible drivers need to be set up in a way that ensures these functions are called if we want to interact with the driver.

3.3.1 AddDevice

All PnP-compatible drivers must define this routine. It is responsible for creating functional device objects (FDO) and filter device objects (filter DO) for devices enumerated by the PnP manager. This explains why AddDevice is where most of the initialization logic resides. That includes: - creation of device objects (IoCreateDevice), - initialization of various internal variables that are later required to reach the vulnerable code, - I/O queue management in WDF (KMDF) drivers.

The MSDN page about managing I/O queues in WDF drivers says: > Drivers typically call WdfIoQueueCreate from within an EvtDriverDeviceAdd callback function. The framework can begin delivering I/O requests to the driver after the driver's EvtDriverDeviceAdd callback function returns.

In the context of WDF (KMDF) drivers, AddDevice is referred to as EvtDriverDeviceAdd (different name, same application).

AddDevice is not called from within the DriverEntry routine, which means it does not automatically execute upon driver load. Instead, the PnP manager invokes it only after it discovers a new device node and determines that this driver should either control the device directly or serve as a filter in the device stack.

Let's look at some code. Note: all structure-specific offsets are for the 64-bit architecture.

Both in DriverEntry and in AddDevice, the first parameter the function receives is a pointer to the DRIVER_OBJECT structure. As we can read on the MSDN page, the structure is allocated by the I/O manager:

The I/O manager allocates the DRIVER_OBJECT structure and passes it as an input parameter to a driver's DriverEntry, AddDevice, and optional Reinitialize routines and to its Unload routine, if any.

DRIVER_OBJECT contains pointers to the driver's dispatch routines, each at a specific offset (e.g. 0xe0 for IRP_MJ_DEVICE_CONTROL).

The pointer to AddDevice, however, is not stored directly in the DRIVER_OBJECT structure, but in the DRIVER_EXTENSION structure, accessed via DriverObject->DriverExtension->AddDevice. This fact is mentioned on the same MSDN page:

Pointer to the driver extension. The only accessible member of the driver extension is DriverExtension->AddDevice, into which a driver's DriverEntry routine stores the driver's AddDevice routine.

So in the decompiler, the AddDevice assignment typically looks like:


// DriverObject->DriverExtension->AddDevice = SomeFunction;
*(*(param_1 + 0x30) + 8) = FUN_XXXXX;

So, a typical initialization sequence for driver dispatch routines and other standard callbacks we can usually find in a device driver's DriverEntry function looks like this (decompiled in Ghidra, comments added manually):


*(code **)(param_1 + 0x70) = FUN_00011a08;  // IRP_MJ_CREATE dispatch routine
*(code **)(param_1 + 0x80) = FUN_00011a08;  // IRP_MJ_CLOSE dispatch routine
*(code **)(param_1 + 0xe0) = FUN_00010614; // IRP_MJ_DEVICE_CONTROL dispatch routine
*(code **)(param_1 + 0xe8) = FUN_000104ac; // IRP_MJ_INTERNAL_DEVICE_CONTROL
*(code **)(param_1 + 0x148) = FUN_00011c70; // IRP_MJ_PNP dispatch routine
*(code **)(param_1 + 0x120) = FUN_00011bc8; // IRP_MJ_POWER dispatch routine
*(code **)(*(longlong *)(param_1 + 0x30) + 8) = FUN_00011ad4; // AddDevice
*(code **)(param_1 + 0x68) = FUN_00011b8c; // DriverUnload

So, AddDevice is defined in FUN_00011ad4 and upon driver load (DriverEntry execution) its pointer is written into DriverObject->DriverExtension->AddDevice, just as all dispatch routine pointers are written into their relevant offsets. But none of those functions have been invoked yet. For example, FUN_00010614 (IRP_MJ_DEVICE_CONTROL) will only execute once the driver receives an IRP with MajorFunction code = IRP_MJ_DEVICE_CONTROL (e.g. , in response to DeviceIoControl call from userland). Likewise, AddDevice is not called by the driver itself, but rather by the PnP manager under specific circumstances.

Now, let's look into FUN_00011ad4 and see how a typical AddDevice implementation looks like:


undefined8 FUN_00011ad4(undefined8 param_1,undefined8 param_2)
{
  longlong lVar1;
  longlong lVar2;
  undefined8 uVar3;
  undefined8 uVar4;
  undefined8 uVar5;
  undefined8 uVar6;
  longlong local_res18 [2];
 
  local_res18[0] = 0;
  lVar1 = *(longlong *)(DAT_00011880 + 0x40);
  uVar3 = IoCreateDevice(param_1,0x100,0,0x22,0,0,local_res18);
  if (-1 < (int)uVar3) {
    lVar2 = *(longlong *)(local_res18[0] + 0x40);
    *(undefined1 *)(lVar2 + 5) = 0;
    *(undefined1 *)(lVar2 + 4) = 0;
    *(undefined8 *)(lVar2 + 0x18) = 0;
    *(undefined8 *)(lVar2 + 0x10) = param_2;
    *(longlong *)(lVar2 + 8) = local_res18[0];
    *(undefined4 *)(lVar2 + 0x20) = 0x10000004;
    ExInterlockedInsertHeadList(lVar1,lVar2 + 0x28,lVar1 + 0x18);
    LOCK();
    *(int *)(lVar1 + 0x10) = *(int *)(lVar1 + 0x10) + 1;
    UNLOCK();
    KeInitializeEvent(lVar2 + 0x50,1);
    *(undefined4 *)(lVar2 + 0x68) = 1;
    *(uint *)(local_res18[0] + 0x30) = *(uint *)(local_res18[0] + 0x30) & 0xffffff7f;
    uVar3 = IoAttachDeviceToDeviceStack(local_res18[0],param_2);
    *(undefined8 *)(lVar2 + 0x18) = uVar3;
    uVar3 = 0;
    local_res18[0] = 0;
    RtlInitUnicodeString(&DAT_00011870,u_\Device\SampleDrv_00012270);
    uVar4 = IoCreateDevice(param_1,0x40,&DAT_00011870,0x22,0,0,local_res18);
    if (-1 < (int)uVar2) {
        RtlInitUnicodeString(&DAT_00011860,u_\DosDevices\SampleDrv_000122a0);
        uVar5 = IoCreateSymbolicLink(&DAT_00011860,&DAT_00011870);
        uVar6 = (ulonglong)uVar5;
        if ((int)uVar5 < 0) {
            IoDeleteDevice(*(undefined8 *)(param_1 + 8));
        }
    }
  }
  return uVar3;
}

As we can see, two separate device objects are created. First, we have the following call to IoCreateDevice, whose returned value is saved in uVar3:


uVar3 = IoCreateDevice(param_1,0x100,0,0x22,0,0,local_res18);

The first param - param_1 - is a pointer to the driver object.

The second parameter is the requested device extension size (0x100) for the newly created device. As the MSDN page says: > The device extension is the most important data structure associated with a device object. Its internal structure is driver-defined, and it's typically used to: > > Maintain device state information. > Provide storage for any kernel-defined objects or other system resources, such as spin locks, used by the driver. > Hold any data the driver must have resident and in system space to carry out its I/O operations.

Device extension (individual for every device object) is not the same thing as driver extension (offset 0x30 in the DRIVER_OBJECT) mentioned earlier (where AddDevice pointer, if present, is stored at offset 0x8). I am emphasizing the difference, because both terms sound similar, which may create confusion. We will get back to the most common application of the device extension structure later in this section.

The third parameter is the device name - in this case, empty (unnamed device object), which is typical for FDOs.

Looking further, after FDO creation, we have a whole block of code, which only executes if device object creation was successful:


if (-1 < (int)uVar3)) {

Several instructions further in that block, we have a call to IoAttachDeviceToStack:


uVar3 = IoAttachDeviceToDeviceStack(local_res18[0],param_2);

In AddDevice callback param_2 holds a pointer to the PDO created by the relevant bus driver.

Since AddDevice is invoked by the PnP manager, both parameters - param_1 pointing at the DRIVER_OBJECT and param_2 pointing at the PDO (DEVICE_OBJECT) - are provided by the PnP manager.

So, at this point, we can clearly see that only if AddDevice is invoked will the driver create its FDO (and attach it to a device stack, making it accessible for IRP processing via handles opened on the PDO).

Most PnP drivers only create one device object (FDO) in their AddDevice, and attach that object to a device stack, on top of the PDO pointed by param_2.

This particular driver, however, also creates a CDO:


Var4 = IoCreateDevice(param_1,0x40,&DAT_00011870,0x22,0,0,local_res18);

Note that the third parameter is not 0 (which means a device name is provided). And there is no IoAttachDeviceToStack call on that device object. So the device object is named and standalone - typical CDO.

Both device objects are IRP entry points, and this driver will only create them when AddDevice is called.

This structure applies to all FDOs and filter DOs. In this particular driver we also have a CDO created in the AddDevice callback.

Additionally, AddDevice is where drivers initialize their custom internal structures, including the ones located in device extension structures. If we look back into the AddDevice function above, we have such an example right in the beginning of the conditional code block, starting with this line:


lVar2 = *(longlong *)(local_res18[0] + 0x40);

local_res18[0] holds a pointer to the device object created by the preceding IoCreateDevice call. In a DEVICE_OBJECT, 0x40 is the offset of the device extension structure. So lVar2 points at the device extension. Then, the next 7 instructions perform various initializations at arbitrary offsets of the device extension structure:


*(undefined1 *)(lVar2 + 5) = 0;
*(undefined1 *)(lVar2 + 4) = 0;
*(undefined8 *)(lVar2 + 0x18) = 0;
*(undefined8 *)(lVar2 + 0x10) = param_2;
*(longlong *)(lVar2 + 8) = local_res18[0];
*(undefined4 *)(lVar2 + 0x20) = 0x10000004;
ExInterlockedInsertHeadList(lVar1, lVar2 + 0x28, lVar1 + 0x18);

The contents of the device extension structure is how WDM drivers usually recognize (make distinction) between device objects used to deliver the current IRP. It makes sense - after all, the device extension is a structure inside the device object, not the driver object. So upon device object creation the driver may put different values into individual device extension fields, so later when a pointer to that device is received in param_1 by a dispatch routine, the routine can read those values and use them in if conditions. Oftentimes, vulnerable code in dispatch routines sits behind such conditional blocks, making vulnerable execution paths depend on the specific device object used to deliver the IRP.

Now it becomes clear why having AddDevice called is crucial:

  1. It is required for the driver to initialize properly, which is oftentimes required for vulnerable code to become reachable from userland. This includes both:
    1. Otherwise-inaccessible conditional code branches.
    2. CDO creation (device object serving as entry point to the driver).
  2. More importantly, the purpose of AddDevice is to create a new PnP-compatible (unnamed FDO/FiDO) device object and attach it to the device stack on top of the PDO provided by the PnPManager in the second argument ([in] _DEVICE_OBJECT *PhysicalDeviceObject). Which means that AddDevice is the function that connects the driver (via its FDO/FiDO) to a newly created device stack, allowing IRP travel.

For each driver, multiple independent interaction (attack) vectors may exist. Their activation depends on proper driver initialization and typically materializes in one of the following forms:

  1. CDOs created from within the AddDevice routine. Most PnP-compatible drivers do not create CDOs, but some do.
  2. FDOs and FiDOs created within AddDevice and attached on top of a newly created device stack. These devices can only be accessed via the stack.

3.3.2 IRP_MJ_PNP

IRP_MJ_PNP is a MajorFunction IRP code dedicated for PnP-related interactions. Each PnP-compatible driver must handle this code with a dedicated dispatch routine, often referred to as DispatchPnP.

As the above MSDN page reads: > Associated with the IRP_MJ_PNP function code are several minor I/O function codes (see Plug and Play Minor IRPs), some of which all drivers must handle and some of which can be optionally handled. The PnP manager uses these minor function codes to direct drivers to start, stop, and remove devices and to query drivers about their devices.

While these routines are not as critical as AddDevice, because they are not responsible for the creation of the PnP-type device object, they usually implement other usual steps of driver initialization logic, such as: - initialization of global driver-internal variables, - configuration file checks, - device interface registration, - hardware probing and validation.

It is worth keeping in mind that there is a difference in how WDM and WDF drivers structure those callbacks in their code. WDM drivers set a traditional IRP_MJ_PNP dispatch routine on the DriverObject->MajorFunction table. Any processing of PnP minor IRPs is handled in that routine. WDF (KMDF) drivers register PnP/power state-change callbacks via WdfDeviceInitSetPnpPowerEventCallbacks, which provides clear separation of functions dedicated for handling individual minor IRPs. These differences become relevant during static analysis and debugging, but they do not affect they way drivers are set up from userland to get those routines properly invoked.

3.4 Active hardware interaction and probing

Only a small fraction of driver code actually interacts with physical hardware.

The relevant direct and indirect interaction mechanisms include: - legacy x86 port I/O (https://ift.tt/N1oFCik, https://ift.tt/96O0Iwv and related IN/OUT instruction wrappers), - Memory-Mapped I/O (https://ift.tt/cqAgRyT, https://ift.tt/NJcbjWZ and variants), - PCI configuration space (https://ift.tt/NfEU5PC), - ACPI control methods (https://ift.tt/UacsYui), - Serial Peripheral Bus (https://ift.tt/ce0FrqC and related SPB I/O requests), - GPIO (https://ift.tt/adntr5F), - DMA (https://ift.tt/owFdH8n), - interrupts (https://ift.tt/uLSn8BV), - calls to other drivers via https://ift.tt/XTgOJHz.

When considering hardware-gated code and by extension hardware-gated vulnerabilities, it is crucial to understand the context. To illustrate this, let's consider three different examples, all involving the same mechanism - MMIO.

3.4.1 Neutral hardware use

Fixed address 0xFEE00000, universally present:


// Local APIC — fixed at 0xFEE00000 on all x86 systems
base = MmMapIoSpace(0xFEE00000, PAGE_SIZE, MmNonCached);
version = READ_REGISTER_ULONG(base + 0x30);
MmUnmapIoSpace(base, PAGE_SIZE);

No hardware-gating, no security impact.

3.4.2 Vulnerable hardware use

In this scenario, we have an arbitrary physical memory write (vulnerable use of MmMapIoSpace, followed by WRITE_REGISTER_ULONG). It is unconditionally reachable - any system running the driver is exposed:


// Physical address and offset supplied by usermode via IOCTL
base = MmMapIoSpace(input->PhysicalAddress, input->Size, MmNonCached);
WRITE_REGISTER_ULONG(base + input->Offset, input->Value);
MmUnmapIoSpace(base, input->Size);

3.4.3 Hardware gating

And here we also have an arbitrary physical memory write, but an attacker can only reach it on machines where the hardware chip ID check passes. That's the hardware gate: the MmMapIoSpace on a non-existent BAR returns NULL or maps to nothing meaningful, and chipId won't match:


// BAR address obtained from PCI config space of a specific device
base = MmMapIoSpace(barAddress, BAR_SIZE, MmNonCached);
chipId = READ_REGISTER_ULONG(base + CHIP_ID_REGISTER);

if (chipId == 0x1234ABCD) {
    WRITE_REGISTER_ULONG(base + input->Offset, input->Value);
}
MmUnmapIoSpace(base, BAR_SIZE);

For more such latest threat research and vulnerability advisory, please subscribe to Atos Cyber Shield blogs.

4 How driver deployment can be approached from the BYOVD perspective

In this section we are going to try to evaluate how much influence over proper driver initialization is possible by solely operating from userland (with administrative privileges), to reflect a typical BYOVD scenario.

So in this section we are not considering techniques involving: - physical access, - hypervisor level access allowing creation of virtualized hardware, - non-standard/insecure system configurations, such as disabled driver signature enforcement, - artificial alterations of execution flow using kernel mode debugger, or any other use of kernel mode debugger.

While the above techniques are all interesting and valuable for security research and testing, they are out of scope of this article.

4.1 Simple sc.exe deployment

This is the simplest, minimal step required to trigger driver load. We create a relevant service entry, then we trigger driver load by starting that service:


sc create SampleDrv type= kernel start= demand binPath= System32\drivers\SampleDrv.sys && sc.exe start

Note, this deployment alone makes the driver execute its DriverEntry, but does not cover any PnP setup. In terms of named device creation, this setup approach is sufficient for drivers matching the pattern described in 3.1 Unconditional creation upon driver load.

Now, if we want to test if the driver created any named device objects, the easiest way not involving WinDBG usage is to:

  1. Use NtObjectManager to list the \Devices directory and save that list.
  2. Deploy and start the driver (sc create + sc start).
  3. Use NtObjectManager again to list the \Devices directory and compare the result with the list obtained in step 1.
  4. If a new device object was detected, try obtaining its SDDL.
  5. Successful reading of SDDL proves it is possible to open a handle from userland, and only these devices are reported.

A Powershell implementation can be found here.

Let's see this script in action.

First, this is what we can expect to see for a driver that loads, but does not create any new devices:


PS C:\test> .\sc_deploy_detect.ps1 C:\runtime_service\IFM63X64.sys
Returning device list (193 elements).
[SC] CreateService SUCCESS

SERVICE_NAME: IFM63X64
        TYPE               : 1  KERNEL_DRIVER
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 0
        FLAGS              :
Returning device list (193 elements).

We can see that the driver was successfully loaded, but the device list did not change after that.

Now, here is an example of a driver that does create a new device right away upon load:


PS C:\test> .\sc_deploy_detect.ps1 C:\runtime_service\KfeCo11x64.sys
Returning device list (193 elements).
[SC] CreateService SUCCESS

SERVICE_NAME: KfeCo11x64
        TYPE               : 1  KERNEL_DRIVER
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 0
        FLAGS              :
Returning device list (194 elements).
New device found for
 \Device\KfeCoDriver (symlink: ) O:BAG:SYD:P(A;;FA;;;SY)(A;;FA;;;BA)

This deployment and device detection approach is fast and practical for runtime discovery of drivers that create userland-accessible CDOs out of the box.

However, it is not sufficient for PnP device objects, which are far more common and thus constitute a much larger attack surface.

Also, keep in mind that many drivers deployed this way will fail to load due to missing dependencies. Those are usually satisfied when the deployment is conducted using the original installer and INF file.

4.2 Creating software-emulated devices with spoofed hardware ID

4.2.1 The idea

After digging a bit and learning more about the driver deployment process, I stumbled upon the test device functionality provided by devcon.exe, which provides the ability to create device nodes with arbitrary (spoofed) hardware IDs.

So it became clear to me that these devices could be used to compensate for the missing hardware and get the AddDevice callback invoked.

Most device drivers come with INF files, which tie drivers to physical hardware by hardware IDs.

The easiest way to identify hardware ID (or IDs) matching a driver is by viewing its INF file. Hardware IDs are located in the Models sections, for example:

Here is a Python implementation extracting hardware IDs from INF files.


[SampleDrv.NTamd64]

%SampleDrv.DeviceDesc% = SampleDrv,ACPI\SAMPLEDRV7853

Once we have a matching hardware ID, instead of explicitly calling sc.exe, we deploy the driver as follows:


pnputil.exe /add-driver SampleDrv.inf /install
devcon.exe install SampleDrv.inf "ACPI\SAMPLEDRV7853"

First, we use pnputil to deploy the driver package into the Windows Driver Store.

Next, we use devcon to create a new software-emulated device node with an arbitrary hardware ID that matches one defined in the driver's INF file. This action triggers the PnP manager to detect the newly staged driver as the best match for the device.

As a result, the driver's AddDevice routine gets executed.

While pnputil.exe is present on every Windows system, devcon.exe is not, but it can be found in WDK.

The algorithm of detecting new named device objects as a result of this deployment approach is the same, except for the deployment commands.

The devcon version of the deploy and detect PowerShell script can be found here.

The output generated by this script looks the same as for the sc.exe version.

4.2.2 Initial test results

My preliminary experiments with this deployment approach resulted in almost twice as many new device objects created as compared to the simple sc.exe create, non-PnP deployment. This clearly demonstrates that software-emulated device nodes with spoofed hardware IDs are a viable userland-only method of making (some) drivers reachable without their relevant hardware. I was able to find and confirm numerous driver vulnerabilities this way, including very good BYOVD candidates.

It is important to note that the algorithm used to detect new named device objects includes both CDOs as well as FDOs attached on top of the software-emulated PDO with an auto-generated name.

In the screenshot below, demonstrating a fragment of the aggregated result log, we can see one CDO and one PDO (with auto-generated name) created by the same driver, both with readable security descriptors:

New named devices created during devcon install

For visibility, the log file also includes newly discovered device objects whose SDDLs could not be obtained. Those make up the majority.

And here we can see 3 PDOs with auto-generated names, whose security descriptors are readable (the additional column is the GLOBAL?? symlink name, in this case automatically created with device interface registration):

New named devices created during devcon install

So, an obvious question arises: Why were the security descriptors of so many device objects created during this test not readable?

And secondly, what are we really doing when running "devcon.exe install path_to.inf HWID"?

To answer these questions, let's have a closer look at the process of software-emulated device creation.

4.2.3 Creating software-emulated devices with SoftwareDevice and PnpManager

Keep in mind that creating a software-emulated device and telling Windows to use a specific driver to drive that device are two separate steps: 1. First, we create a software-emulated device with a spoofed hardware ID. 2. Then we invoke the driver installation/update process for that device using the original INF file (UpdateDriverForPlugAndPlayDevicesW), to eventually run the driver on the emulated device.

When it comes to the first step, the Windows kernel itself provides two similar mechanisms allowing creation of software-emulated devices with arbitrary hardware IDs:

  1. The first method is provided by the PnPManager driver itself, and it can be performed by using Config Manager API/SetupAPI. This is how devcon.exe implements its software-emulated device creation.
  2. The second one is provided by the SoftwareDevice driver, using Software Device API.

Both drivers are embedded in ntoskrnl.exe. In both cases we are creating PnP device nodes with arbitrary hardware IDs.

Let's have a closer look into this process.

4.2.3.1 SetupAPI and PnpManager - process overview

Setting up a software-emulated device using SetupAPI requires the following sequence of API calls:

  1. SetupDiCreateDeviceInfoList - create an empty device info set for our class.
  2. SetupDiCreateDeviceInfoW - create device node.
  3. SetupDiSetDeviceRegistryPropertyW - set the hardware ID on the devnode.
  4. SetupDiCallClasInstaller - register the device with PnP.
  5. UpdateDriverForPlugAndPlayDevicesW - force driver update for provided HWID, using provided INF file.

Calling SetupDiCallClassInstaller (step 4) triggers a sequence of operations on the kernel level, including a call to IoCreateDevice (PnpManager creating the new device object).

UpdateDriverForPlugAndPlayDevicesW requests the PnP manager to install a driver for that device. Before that happens, the device will show DOE_START_PENDING in its extension flags, when inspected with !devobj in WinDBG:


0: kd> !devobj \Device\0000003b
...
ExtensionFlags (0x00000810)  DOE_START_PENDING, DOE_DEFAULT_SD_PRESENT
...

Once the driver is bound to the device, the target driver's AddDevice will be invoked by PnpManager, passing a pointer to the PDO (owned by PnpManager) as the second argument. AddDevice is expected to create its FDO and attach it on top of the PDO using IoAttachDeviceToDeviceStack.

4.2.3.2 SetupAPI and PnpManager - device node creation only

Let's use the following C implementation of steps 1-4, to only create a new device node with an arbitrary hardware ID, then inspect the device node in Device Manager and inspect its named device object in WinDBG.

This way we can skip using an INF file entirely (for now) and examine the newly created named device object in its default state, without the PnP manager making any attempts to build a device stack on top of it.


create_swdev_cm.exe FAKEHW_ID
Device node created successfully for hardware ID: FAKEHW_ID

We should be able to see the new device node (as "Unknown") in Software Devices in the Device Manager view.

We can manually select and view different device node properties, such as device instance path, hardware ID and even PDO name:

Device manager view - instance path
Device manager view - hardware ID
Device manager view - Physical Device Object name

Let’s inspect the PDO name in WinDBG:


0: kd> !devobj \Device\00000036
Device object (ffff8207ddc03300) is for:
 00000036 \Driver\PnpManager DriverObject ffff8207d8aa3290
Current Irp 00000000 RefCount 0 Type 00000004 Flags 00001040
SecurityDescriptor ffffd408ceb1d260 DevExt ffff8207ddc03450 DevObjExt ffff8207ddc03458 DevNode ffff8207deca0660
ExtensionFlags (0x00000800)  DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000080)  FILE_AUTOGENERATED_DEVICE_NAME
Device queue is not busy.

We can see that the driver owning the device object is \Driver\PnpManager, the device object has an auto-generated name and a default (permissive) security descriptor. Also note that the device object is NOT attached to any device stack here (there is no AttachedDevice etc.), so we can rule out a filter blocking access to it from above.

Examining the security descriptor in WinDBG confirms the default, permissive security descriptor:


0: kd> !sd ffffd408ceb1d260
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8814
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SACL_AUTO_INHERITED
            SE_SELF_RELATIVE
->Owner   : S-1-5-32-544
->Group   : S-1-5-21-557163823-2925933541-2346282345-513
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x5c
->Dacl    : ->AceCount   : 0x4
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x14
->Dacl    : ->Ace[0]: ->Mask : 0x001201bf
->Dacl    : ->Ace[0]: ->SID: S-1-1-0

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0x0
->Dacl    : ->Ace[1]: ->AceSize: 0x14
->Dacl    : ->Ace[1]: ->Mask : 0x001f01ff
->Dacl    : ->Ace[1]: ->SID: S-1-5-18

->Dacl    : ->Ace[2]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[2]: ->AceFlags: 0x0
->Dacl    : ->Ace[2]: ->AceSize: 0x18
->Dacl    : ->Ace[2]: ->Mask : 0x001f01ff
->Dacl    : ->Ace[2]: ->SID: S-1-5-32-544

->Dacl    : ->Ace[3]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[3]: ->AceFlags: 0x0
->Dacl    : ->Ace[3]: ->AceSize: 0x14
->Dacl    : ->Ace[3]: ->Mask : 0x001200a9
->Dacl    : ->Ace[3]: ->SID: S-1-5-12

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000001
->Sacl    : ->Ace[0]: ->SID: S-1-16-4096

But when we try to display the security descriptor with NtObjectManager, we will encounter the following error message:

Failure attempting to read SDDL of unattached PDO

The requested operation is not valid for the target device?

In the introduction article, in section 3.6.7 Filters as access control, I demonstrated a similar situation, only with Access denied. In that case the upper driver in the stack was blocking IRP_MJ_CREATE, so the IRP never even reached the named PDO down the stack (the one used to open the handle).

Since here we only have one device object instead of a device stack, it must be PnpManager itself blocking those requests.

Let's have a look at its dispatch routine table:


0: kd> !drvobj PnpManager 2
Driver object (ffff8207d8aa3290) is for:
 \Driver\PnpManager
...
Dispatch routines:
[00] IRP_MJ_CREATE                      fffff8053ff516b0        nt!IopInvalidDeviceRequest
[01] IRP_MJ_CREATE_NAMED_PIPE           fffff8053ff516b0        nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       fffff8053ff516b0        nt!IopInvalidDeviceRequest
[03] IRP_MJ_READ                        fffff8053ff516b0        nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE                       fffff8053ff516b0        nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION           fffff8053ff516b0        nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION             fffff8053ff516b0        nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA                    fffff8053ff516b0        nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      fffff8053ff516b0        nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               fffff8053ff516b0        nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fffff8053ff516b0        nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      fffff8053ff516b0        nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           fffff8053ff516b0        nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         fffff8053ff516b0        nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              fffff8053ff516b0        nt!IopInvalidDeviceRequest
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fffff8053ff516b0        nt!IopInvalidDeviceRequest
[10] IRP_MJ_SHUTDOWN                    fffff8053ff516b0        nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                fffff8053ff516b0        nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP                     fffff8053ff516b0        nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT             fffff8053ff516b0        nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              fffff8053ff516b0        nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY                fffff8053ff516b0        nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER                       fffff8054015fa10        nt!IopPowerDispatch
[17] IRP_MJ_SYSTEM_CONTROL              fffff80540561f30        nt!IopSystemControlDispatch
[18] IRP_MJ_DEVICE_CHANGE               fffff8053ff516b0        nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 fffff8053ff516b0        nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   fffff8053ff516b0        nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         fffff805402c6940        nt!IopPnPDispatch

Aha! The dispatch routine values for most MajorFunction codes are set to nt!IopInvalidDeviceRequest.

Which means that the driver simply does not support them.

Without IRP_MJ_CREATE we cannot open a handle, even to read the security descriptor. In the case described in the introduction article (section 3.6.7 Filters as access control), the upper driver called IofCompleteRequest, with Irp->IoStatus.Status = STATUS_ACCESS_DENIED.

In this case, IRP_MJ_CREATE returns Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST.

The reason this is happening is because the driver owning the PDO is simply not intended to be responsible for handling IRP_MJ_CREATE requests. In typical device stacks, the handling of IRP_MJ_CREATE should take place in the FDO and end there (with IofCompleteRequest), with IRP_MJ_CREATE never being passed down the stack.

Which leads us to an important conclusion - if we are trying to open a handle to a device stack, at least one device in that stack must successfully handle our IRP_MJ_CREATE.

We cannot open a handle to a device stack if neither of the following accepts IRP_MJ_CREATE:

  1. Upper FiDO (if present).
  2. FDO.
  3. Lower FiDO (if present).
  4. The PDO (if IRP ever reaches here). PDO is always the base of a device stack, so it's always present.

This is why we cannot open a handle to a bare (non-stack-attached) named PDO created by PnpManager.

A large portion of the failed deployment attempts observed in the aggegated log - where no security descriptors could be obtained for the newly created devices - was caused by the lack of IRP_MJ_CREATE support in the PnP Manager, combined with the absence of an upper-level driver in the device stack to handle that IRP.

Which is what happened when: - UpdateDriverForPlugAndPlayDevicesW succeeded, but the target driver did not support IRP_MJ_CREATE either, - UpdateDriverForPlugAndPlayDevicesW failed for any reason (.cat file missing, other dependancy referred in the INF file missing, or even the driver not loading).

4.2.3.3 SetupAPI and PnpManager - complete and successful deployment

Now, for contrast, let's see how a full (steps 1-5) and successful deployment looks like, using the Powershell script.

We will use AwinicSmartKAmps.sys (I2C smart amplifier controller) driver as an example.

First, let's have a look at its INF file.

On line 32 we can find the hardware ID - ACPI\AWDZ8399.

It is also worth noting that on line 50 the "AddService" directive defines the driver's service name as AwinicChip.

This is how the driver object will be named, even though the .sys file itself is named AwinicSmartKAmps.sys (as visible on line 58):

Hardware ID from INF file

We run the deployment script:

Hardware ID from INF file

Interesting - two new named device objects were detected, and they are both userland-accessible (SDDLs could be retrieved)!

Let's inspect the driver object in WinDBG:


!drvobj AwinicChip 7
Driver object (ffffe18f33ff3e10) is for:
 \Driver\AwinicChip

Driver Extension List: (id , addr)
(fffff805394622e0 ffffe18f2ec1a950)
Device Object list:
ffffe18f32ce6de0

DriverEntry:   fffff80562ba0630 AwinicSmartKAmps
DriverStartIo: 00000000
DriverUnload:  fffff80562ba07c0 AwinicSmartKAmps
AddDevice:     fffff80539462090

Dispatch routines:
[00] IRP_MJ_CREATE                      fffff80539427ac0        +0xfffff80539427ac0
...
Device Object stacks:

!devstack ffffe18f32ce6de0 :
  !DevObj           !DrvObj            !DevExt           ObjectName
  ffffe18f34e51e00  \Driver\ksthunk    ffffe18f34e51f50  0000002e
> ffffe18f32ce6de0  \Driver\AwinicChip ffffe18f35cde310
  ffffe18f35b0db90  \Driver\PnpManager ffffe18f35b0dce0  0000002d
!DevNode ffffe18f32f32b20 :
  DeviceInst is "ROOT\MEDIA\0000"
  ServiceName is "AwinicChip"

We can see that our driver created one device object (ffffe18f32ce6de0), which was then attached into a device stack on top of \Device\0000002d (software-emulated PDO created by PnpManager), and additionally to that, the PnP manager also attached another (also named) device object on top of it - \Device\0000002e (owned by \Driver\ksthunk).

If we look at the beginning of the INF file, we'll notice this:

Hardware ID from INF file

The driver class is defined as Multimedia, using the well-known {4d36e96c-e325-11ce-bfc1-08002be10318} GUID.

ksthunk (Kernel Streaming) is registered as a class upper filter for the MEDIA device setup class.

This can be confirmed by inspecting the UpperFilters REG_MULTI_SZ registry entry at HKLM\SYSTEM\CurrentControlSet\Control\Class\{4d36e96c-e325-11ce-bfc1-08002be10318}:

Hardware ID from INF file

The PnP manager automatically attaches class upper filter device objects to every device in the class it is set up for. That's why \Device\0000002e owned by \Driver\ksthunk is present in the device stack on top of our driver's unnamed FDO.

We will revisit the UpperFilters mechanism later in this article.

Another consequence of the driver being installed as a Media device is how its device node is visible in the Device Manager GUI tool. It appears in the "Sound, video and game controllers" subtree:

Hardware ID from INF file
Hardware ID from INF file

Before we move on, while we already have the driver loaded, let's set up a couple of breakpoints:


0: kd> bp fffff80539462090 ".echo AddDevice called;g"
0: kd> bp fffff80539427ac0 ".echo IRP_MJ_CREATE called;g"
0: kd> g

We already know these addresses from the output of !drvobj AwinicChip 7.

Now, IRP_MJ_CREATE should hit whenever we attempt to open a handle to any device in the stack:

Invoking IRP_MJ_CREATE

In the debugger output we should see:


IRP_MJ_CREATE called
IRP_MJ_CREATE called

And if we manually invoke the creation of another device node using the same hardware ID (by simply running devcon.exe install AwinicSmartKAmps.inf "ACPI\AWDZ8399" again), we should see the AddDevice breakpoint hitting as well:


AddDevice called

Keep in mind that AddDevice being invoked only means that we have managed to trick the PnP manager to call it. It does not neccessarily mean that AddDevice will successfully create a new device object and attach it to the the device stack - it may still fail internally due to additional unmet conditions.

From the practical perspective, the easiest way to confirm the success of this type of deployment, is reading the security descriptor of the software-emulated device. If that works, it means that: - driver installation (UpdateDriverForPlugAndPlayDevicesW call) was successful, - in the newly created device stack there is a driver that accepts IRP_MJ_CREATE.

Here is the full version the setup program (steps 1-5). Requires INF file.

4.2.3.4 Software Device API

An alternative to the SetupAPI device creation approach is Software Device API.

Creation of a software-emulated device with arbitrary hardware ID is simpler than with SetupAPI, as it boils down to just calling SwDeviceCreate.

A sample C implementation can be found here. It can be easily extended with UpdateDriverForPlugAndPlayDevicesW (requires INF file).

By default the device object gets removed when we close the HSWDEVICE hSwDevice handle (the handle populated by SwDeviceCreate). To prevent that, before closing the handle, the program calls hr = SwDeviceSetLifetime(hSwDevice, SWDeviceLifetimeParentPresent);.

Device objects created this way are owned by \Driver\SoftwareDevice (as a reminder, the ones created with SetupAPI are owned by \Driver\PnpManager).

Hardware ID spoofed with SoftwareDevice API
Hardware ID spoofed with SoftwareDevice API

A quick inspection of the device object is WinDBG:


0: kd> !devobj \Device\00000036
0: kd> Device object (ffffaa0a87a6de00) is for:
 00000036 \Driver\SoftwareDevice DriverObject ffffaa0a8273ce00
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00001040
SecurityDescriptor ffffce8086380820 DevExt ffffaa0a87a6df50 DevObjExt ffffaa0a87a6df60 DevNode ffffaa0a86a1a8e0
ExtensionFlags (0x00000800)  DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000180)  FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) ffffaa0a88033de0 \Driver\AwinicChip
Device queue is not busy.

And just like \Driver\PnpManager, the \Driver\SoftwareDevice driver does not support IRP_MJ_CREATE:


0: kd> !drvobj SoftwareDevice 2

Driver object (ffffaa0a8273ce00) is for:
 \Driver\SoftwareDevice

DriverEntry:   fffff8074ad32f40 nt!PiSwPdoDriverEntry
DriverStartIo: 00000000
DriverUnload:  00000000
AddDevice:     fffff8074a9e4400 nt!ArbPreprocessEntry

Dispatch routines:
[00] IRP_MJ_CREATE                      fffff8074a5516b0        nt!IopInvalidDeviceRequest

4.3 Jumping device stacks

Making a PnP-compatible driver reachable with a software-emulated device is an example of building and accessing a custom device stack.

That opens the way to even interact via IRP with drivers that were never intended for userland interaction (e.g. not checking IRP's RequestorMode prior to further processing), because when deployed the original way they reside in device stacks where an upper driver prevents userland access (by denying or not supporting IRP_MJ_CREATE), just like in the example demonstrated in section 3.6.7 Filters as access control in the introduction article.

So, if we find a vulnerability in a driver that cannot be interacted with from userland in its original configuration, that driver is not useful for Local Privilege Escalation. But for breaking the admin to kernel boundary it might. We just need to deploy it in a way that allows exploitability, by placing it in a device stack where the original upper filter is not present. We could call this malicious driver misconfiguration/deployment.

This approach is not only useful for testing, but also for making vulnerabilities reachable, and thus potentially exploitable during BYOVD attacks.

Let's see another variant of this.

4.3.1 Filter restacking

Once we understand that in order to access a device stack, one of its drivers must accept our IRP_MJ_CREATE, it becomes clear why the deployment scenario covered in section 4.3 could not succeed for filter drivers.

A typical filter driver is a pass-through IRP forwarder, which means it does not reject IRP_MJ_CREATE, but it does not accept it either. It does have the relevant dispatch routine, and that routine usually just forwards IRPs down the stack.

So, if we try to access a filter driver by putting it on top of a software-emulated PDO, we won't be able to open a handle.

That is because neither of the two drivers supports IRP_MJ_CREATE.

The filter driver forwards it down to the PDO, and the PDO rejects it via nt!IopInvalidDeviceRequest.

This is why for filter drivers we need a device stack with a typical FDO below (or just any driver that will accept IRP_MJ_CREATE from userland).

One way to build such a device stack is by abusing the Disk Drive class (GUID {4d36e967-e325-11ce-bfc1-08002be10318}).

The algorithm is as follows:

  1. Deploy the filter driver with sc.exe create (no INF files involved)
  2. Append the filer's service name to the UpperFilters registry key for the device Disk Drive device class in HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e967-e325-11ce-bfc1-08002be10318}.
  3. Mount a new hard drive from VHD, creating a new device node and PnP building a new device stack for it.
  4. Filter becomes accessible once we open a handle on \\.\PhysicalDrive0.

A PowerShell implementation can be found here.

This deployment scenario works for most filter drivers, regardless of the device class the filter is intended for. This way I successfully ran (and in some cases exploited) filter drivers for various device clasess, such as disk, bluetooth, mouse, keyboard and audio.

For instance, here we are loading a gaming mouse filter driver on top of a disk stack:

Running a gaming mouse filter on top of disk stack

If we inspect the driver object in WinDBG, we will see the entire device stack, including GMLXDFltr with its unnamed FiDO on top of it:


0: kd> !drvobj GMLXDFltr
Driver object (ffffd20a9c4cde20) is for:
 \Driver\GMLXDFltr

Driver Extension List: (id , addr)

Device Object list:
ffffd20a9b8a3690  ffffd20a9753b910
0: kd> !devobj ffffd20a9b8a3690
Device object (ffffd20a9b8a3690) is for:
  \Driver\GMLXDFltr DriverObject ffffd20a9c4cde20
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00000000
SecurityDescriptor ffffe48d055db5a0 DevExt ffffd20a9b8a37e0 DevObjExt ffffd20a9b8a3868
ExtensionFlags (0000000000)
Characteristics (0x00000100)  FILE_DEVICE_SECURE_OPEN
AttachedTo (Lower) ffffd20a9ba1f690 \Driver\partmgr
Device queue is not busy.
0: kd> !devstack ffffd20a9b8a3690
  !DevObj           !DrvObj            !DevExt           ObjectName
> ffffd20a9b8a3690  \Driver\GMLXDFltr  ffffd20a9b8a37e0
  ffffd20a9ba1f690  \Driver\partmgr    ffffd20a9ba1f7e0
  ffffd20a9f187060  \Driver\disk       ffffd20a9f1871b0  DR1
  ffffd20aa30e8050  \Driver\vhdmp      ffffd20aa30e81a0  00000038
!DevNode ffffd20a9b8998c0 :
  DeviceInst is "SCSI\Disk&Ven_Msft&Prod_Virtual_Disk\2&1f4adffe&0&000001"
  ServiceName is "disk"

Let's set a breakpoint on its IRP_MJ_CREATE dispatch routine. First, obtain the address:


0: kd> !drvobj GMLXDFltr 2
Driver object (ffffd20a9c4cde20) is for:
 \Driver\GMLXDFltr

DriverEntry:   fffff80085a52244 GMLXDFltr
DriverStartIo: 00000000
DriverUnload:  fffff80085a51b8c GMLXDFltr
AddDevice:     fffff80085a51ab4 GMLXDFltr

Dispatch routines:


[00] IRP_MJ_CREATE                      fffff80085a51a08        GMLXDFltr+0x1a08

Then set up a breakpoint, resume execution:


0: kd> bp GMLXDFltr+0x1a08 ".echo GMLXDFltr IRP_MJ_CREATE called;g"
0: kd> g

On the VM, resolve the device path and trigger IRP_MJ_CREATE by attempting to read the SDDL:


PS C:\> Get-NtSymbolicLinkTarget '\GLOBAL??\PhysicalDrive1'
\Device\Harddisk1\DR1
PS C:\> Get-NtSecurityDescriptor '\Device\Harddisk1\DR1'

Owner                  DACL ACE Count SACL ACE Count Integrity Level
-----                  -------------- -------------- ---------------
BUILTIN\Administrators 5              NONE           NONE

Debugger output:


0: kd> g
GMLXDFltr IRP_MJ_CREATE called
GMLXDFltr IRP_MJ_CREATE called

So, it is working! We are accessing a gaming mouse filter driver via a handle opened on \Device\Harddisk1\DR1!

On a side note, surprisingly, the breakpoint is activated twice, not once. Why?

To find out, let's start with confirming the identity of the userland process. For that we can attach the following script to run when the breakpoint hits, so it prints the image name of the current userland caller:


bp GMLXDFltr+0x1a08 ".echo GMLXDFltr IRP_MJ_CREATE ran;.scriptrun C:\\Users\\ewilded\\curr_image_name.js;g"
breakpoint 0 redefined
0: kd> g

And now, when Get-NtSecurityDescriptor '\Device\Harddisk1\DR1' is invoked again, we get:


GMLXDFltr IRP_MJ_CREATE ran
JavaScript script successfully loaded from 'C:\Users\ewilded\WinDBG_scripts\curr_image_name.js'
Current Process Image Name: powershell.exe
GMLXDFltr IRP_MJ_CREATE ran
JavaScript script successfully loaded from 'C:\Users\ewilded\WinDBG_scripts\curr_image_name.js'
Current Process Image Name: powershell.exe

So in both cases the caller is powershell.

Let's also print the DesiredAccess via IRP IO_STACK_LOCATION, to get more details:


1: kd> g
GMLXDFltr IRP_MJ_CREATE ran
Current Process Image Name: powershell.exe
GMLXDFltr+0x1a08:
fffff800`85a51a08 48895c2408      mov     qword ptr [rsp+8],rbx
2: kd> dt nt!_IRP @rdx Tail.Overlay.CurrentStackLocation
   +0x078 Tail                              :
      +0x000 Overlay                           :
         +0x040 CurrentStackLocation              : 0xffffd20a`9d4806f8 _IO_STACK_LOCATION
2: kd> dt nt!_IO_STACK_LOCATION 0xffffd20a`9d4806f8 Parameters.Create.SecurityContext
   +0x008 Parameters                        :
      +0x000 Create                            :
         +0x000 SecurityContext                   : 0xfffff504`0ab565d0 _IO_SECURITY_CONTEXT
2: kd> dt nt!_IO_SECURITY_CONTEXT 0xfffff504`0ab565d0 DesiredAccess
   +0x010 DesiredAccess : 0x20000

So during the first call of GMLXDFltr+0x1a08, the DesiredAccess is 0x20000. Let's resume execution and inspect the same property on the second breakpoint hit:


2: kd> g
GMLXDFltr IRP_MJ_CREATE ran
Current Process Image Name: powershell.exe
GMLXDFltr+0x1a08:
fffff800`85a51a08 48895c2408      mov     qword ptr [rsp+8],rbx
0: kd> dt nt!_IRP @rdx Tail.Overlay.CurrentStackLocation
   +0x078 Tail                              :
      +0x000 Overlay                           :
         +0x040 CurrentStackLocation              : 0xffffd20a`9dc4a808 _IO_STACK_LOCATION
0: kd> dt nt!_IO_STACK_LOCATION 0xffffd20a`9dc4a808 Parameters.Create.SecurityContext
   +0x008 Parameters                        :
      +0x000 Create                            :
         +0x000 SecurityContext                   : (null)

So, in the first call, DesiredAccess is 0x20000. But in the second one, the SecurityContext is NULL. This strongly suggests that in this second call, we are not dealing with IRP_MJ_CREATE at all.

Let's examine the call stacks when the breakpoint hits.

First:


GMLXDFltr+0x1a08:
fffff800`85a51a08 48895c2408      mov     qword ptr [rsp+8],rbx
3: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff504`0ab56448 fffff800`68eebef5     GMLXDFltr+0x1a08
01 fffff504`0ab56450 fffff800`692f753e     nt!IofCallDriver+0x55
02 fffff504`0ab56490 fffff800`692f2874     nt!IopParseDevice+0x8be
03 fffff504`0ab56660 fffff800`692f1222     nt!ObpLookupObjectName+0x1104
04 fffff504`0ab567f0 fffff800`692eecb1     nt!ObOpenObjectByNameEx+0x1f2
05 fffff504`0ab56920 fffff800`6934f438     nt!IopCreateFile+0x431
06 fffff504`0ab569e0 fffff800`6902bbe5     nt!NtOpenFile+0x58
07 fffff504`0ab56a70 00007ffe`e51af9d4     nt!KiSystemServiceCopyEnd+0x25
08 00000010`96acd9d8 00007ffe`7209aa32     0x00007ffe`e51af9d4
09 00000010`96acd9e0 00000000`00020000     0x00007ffe`7209aa32
0a 00000010`96acd9e8 00000010`96acda10     0x20000
0b 00000010`96acd9f0 00000010`96acdae0     0x00000010`96acda10
0c 00000010`96acd9f8 00000142`0a263498     0x00000010`96acdae0
0d 00000010`96acda00 00000000`00000005     0x00000142`0a263498
0e 00000010`96acda08 00000000`00000000     0x5

We can see nt!NtOpenFile in the Call Site column, which is expected, and confirms this is the handle-opening call.

Let's resume the execution and examine the second hit:


3: kd> g
GMLXDFltr IRP_MJ_CREATE ran
Current Process Image Name: powershell.exe
GMLXDFltr+0x1a08:
fffff800`85a51a08 48895c2408      mov     qword ptr [rsp+8],rbx
3: kd> k
 # Child-SP          RetAddr               Call Site
00 fffff504`0ab56828 fffff800`68eebef5     GMLXDFltr+0x1a08
01 fffff504`0ab56830 fffff800`692f9bdc     nt!IofCallDriver+0x55
02 fffff504`0ab56870 fffff800`692f352e     nt!IopDeleteFile+0x13c
03 fffff504`0ab568f0 fffff800`68eec627     nt!ObpRemoveObjectRoutine+0x7e
04 fffff504`0ab56950 fffff800`693437d4     nt!ObfDereferenceObjectWithTag+0xc7
05 fffff504`0ab56990 fffff800`693403a9     nt!ObpCloseHandle+0x2a4
06 fffff504`0ab56ab0 fffff800`6902bbe5     nt!NtClose+0x39
07 fffff504`0ab56ae0 00007ffe`e51af554     nt!KiSystemServiceCopyEnd+0x25
08 00000010`96acd958 00007ffe`71e3db77     0x00007ffe`e51af554
09 00000010`96acd960 00007ffe`71e06bf0     0x00007ffe`71e3db77
0a 00000010`96acd968 00000000`00000d28     0x00007ffe`71e06bf0
0b 00000010`96acd970 00000142`1f783590     0xd28
0c 00000010`96acd978 00007ffe`d10a4bce     0x00000142`1f783590
0d 00000010`96acd980 00004f02`50afaf79     0x00007ffe`d10a4bce
0e 00000010`96acd988 00007ffe`d17b6370     0x00004f02`50afaf79
0f 00000010`96acd990 00000010`96acdb80     0x00007ffe`d17b6370
10 00000010`96acd998 00007ffe`71e06bf0     0x00000010`96acdb80
11 00000010`96acd9a0 00007ffe`71e06bf0     0x00007ffe`71e06bf0
12 00000010`96acd9a8 00000010`96acd960     0x00007ffe`71e06bf0
13 00000010`96acd9b0 00007ffe`71e3db77     0x00000010`96acd960
14 00000010`96acd9b8 00000010`96acda00     0x00007ffe`71e3db77
15 00000010`96acd9c0 00007ffe`71e06bf0     0x00000010`96acda00
16 00000010`96acd9c8 00000010`96acdae0     0x00007ffe`71e06bf0
17 00000010`96acd9d0 00000142`0a263798     0x00000010`96acdae0
18 00000010`96acd9d8 00000142`1f783590     0x00000142`0a263798
19 00000010`96acd9e0 00000142`0a2590f0     0x00000142`1f783590
1a 00000010`96acd9e8 00000000`00000000     0x00000142`0a2590f0

So, the second call is not IRP_MJ_CREATE. It is IRP_MJ_CLOSE (powershell closing the handle), and it comes from NtClose (visible in the Call Site colum).

The breakpoint hits twice, because GMLXDFltr uses the same dispatch routine for both MajorCodes.

A glance at the dispatch table confirms this. Both IRP_MJ_CREATE and IRP_MJ_CLOSE are set to GMLXDFltr+0x1a08:


0: kd> !drvobj GMLXDFltr 2
Driver object (ffffd20a9c4cde20) is for:
 \Driver\GMLXDFltr

DriverEntry:   fffff80085a52244 GMLXDFltr
DriverStartIo: 00000000
DriverUnload:  fffff80085a51b8c GMLXDFltr
AddDevice:     fffff80085a51ab4 GMLXDFltr

Dispatch routines:
[00] IRP_MJ_CREATE                      fffff80085a51a08        GMLXDFltr+0x1a08
[01] IRP_MJ_CREATE_NAMED_PIPE           fffff80085a50388        GMLXDFltr+0x388
[02] IRP_MJ_CLOSE                       fffff80085a51a08        GMLXDFltr+0x1a08

4.3.2 Per-device and per-class filters

Global per-class device filters can be set up in the LowerFilters and UpperFilters entries in the relevant device class registry locations:


HKLM:\SYSTEM\CurrentControlSet\Control\Class\{GUID}\

For example:


HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4d36e967-e325-11ce-bfc1-08002be10318}\UpperFilters

for Disk Drives.

The list of well-known GUIDs representing device classes can be found here. As we can see, Storage Volumes, Disk Drives and Storage Disks constitute separate, although similar device classes.

UpperFilters and LowerFilters can also be set up for device nodes more selectively - per instance ID instead of device class.

These are located in HKLM\SYSTEM\CurrentControlSet\Enum\<instance_ID>. For example: - HKLM\SYSTEM\CurrentControlSet\Enum\SWD\HWID\0001 (SWD for devices created via the Software Device API), - HKLM\SYSTEM\CurrentControlSet\Enum\ROOT\HWID2\0000 (ROOT for root-enumerated devices created through SetupAPI).

This provides more flexibility in the ways filters can be run. We could use a software-emulated device with an FDO that supports IRP_MJ_CREATE only to run the filter on top of it, without creating a Disk Drive device node by mounting a disk.

4.4 Forced driver replacement

The real reason for using software-emulated devices as described in the previous sections is to get the driver properly initialized by tricking the PnP manager to call its AddDevice callback with a pointer to a PDO.

An alternative to that approach is using one of the devices already existing in the system.

So, can we simply force our driver to be installed and loaded for an existing piece of hardware, replacing the original one?

One reason against this approach is system stability.

If we force an incorrect driver on a piece of hardware, it is reasonable to assume that hardware will stop functioning correctly. To alleviate that risk we could select a device that is not critical and whose disruption is unlikely to be even noticed.

Another potential cause of critical disruption (resulting in system crash) is if the newly loaded driver makes a reference to the PDO's device extension at some specific offset that exists in the PDO the driver was designed to work with. So it is a matter of trial and error to find a suitable device node for this purpose.

Assuming that the target device is similar enough to the device the vulnerable driver was developed, or the driver is simple enough, it could work (depending on the driver).

So, what is stopping us from trying this?

During normal driver installation process, forcing an arbitrary driver on an arbitrary device node is problematic because of the hardware ID missmatch. PnP matches the hardware ID (reported by the bus driver owning the PDO) against all INF files in the local Windows driver repository.

PnP will not recognize a driver as correct if the device node's hardware ID does not match the hardware ID in the corresponding INF file.

The INF file is what ties a driver to a specific hardware ID.

4.4.1 The problem with INF files

So, can we use a custom INF file, which simply ties FAKEHWID_1234 to AwinicSmartKAmps.sys instead?

Let's see.

First, let's create a new device node with an arbitrary hardware ID, using create_swdev_cm.c:


create_swdev_cm.exe FAKEHWID_1234
Device node created successfully for hardware ID: FAKEHWID_1234

We can inspect it in Device Manager. It appears in the Software Devices group because of the class GUID hardcoded in create_swdev_cm.c, but that can be changed if needed. We can clearly see the arbitrary hardware ID and and that at this stage there is no driver installed for the device:

Software device with arbitrary hardware ID and no driver installed
Software device with arbitrary hardware ID and no driver installed

The PnP manager has not found any matching drivers, because FAKEHWID_1234 is not matched by any INF file from the driver repository. This is expected.

Now, let's see what happens when we try to update the driver using a minimum custom INF file, tying FAKEHWID_1234 to AwinicSmartKAmps.sys:

Custom INF file tying FAKEHWID_1234 to AwinicSmartKAmps.sys

If we right-click on the "Unknown device" and invoke "Update driver", we can manually point the directory with the INF file after choosing: "Browse my computer" -> "Let me pick from a list of available drivers on my computer" -> "Have disk", we'll see a warning "This driver is not digitally signed!":

Custom INF file - driver not signed warning

If we ignore the warning and click "Next", we'll see this:

Custom INF file - installation interrupted

This is because files in a driver package, including the INF file, are protected by digital signatures defined in the catalog file (.cat).

If we remove the CatalogFile = AwinicSmartKAmps.cat line entirely and try again, we will end up with the same outcome and a slightly different error message:

Custom INF file - installation interrupted

This logic is implemented behind UpdateDriverForPlugAndPlayDevicesW - if we try to do this programmatically instead of using GUI, we will reach the same outcome, with UpdateDriverForPlugAndPlayDevicesW returning relevant error codes.

4.4.2 Bypassing the INF file mechanism

If we dig deeper into the PnP architecture, we will discover that what really ties a driver to a device node is dedicated registry structures.

Depending on whether we are creating a new device, or forcing our driver on an existing one, we can either directly create new registry structures or modify the existing ones, to make them reflect the state normally attained with a successful installation, effectively skipping the entire INF mechanism.

It boils down to: 1. Deploying the driver with sc.exe create, the standard way. 2. Choosing the target device (for this test we will create one using SetupAPI). 3. Creating the relevant registry structures in the following locations, tying the driver's service name to the device: - SYSTEM\\CurrentControlSet\\Enum\\<DEVICE_INSTANCE_ID>, - SYSTEM\\CurrentControlSet\\Control\\Class\\<GUID>\\<INSTANCE_INDEX>. 4. Restarting the device (triggers PnP to load the driver).

The device instance ID is the full path that uniquely identifies a device in the system and is the actual identifier of a device node.

It makes sense when we think about it. The hardware ID only identifies the make and model, and using it as the device node identifier would prevent the OS from supporting multiple devices with the same hardware ID connected to the system at the same time.

Device instance ID has the following form: <Enumerator>\<DeviceID>\<InstanceID>, for example: PCI\VEN_8086&DEV_A370&SUBSYS_02348086&REV_10\3&11583659&0&F5.

Instance ID is just the last segment - the part that disambiguates multiple devices with the same enumerator and device ID. In the example above, that's 3&11583659&0&F5. It identifies which physical/logical device this is among devices sharing the same enumerator and device ID.

In our case the device instance ID will be ROOT\SOFTWAREDEVICE\0000, whereas: - ROOT is the enumerator name, - SOFTWAREDEVICE is the device ID (the value comes from uppercase conversion of the string passed as the second argument to SetupDiCreateDeviceInfoW), - 0000 is the instance ID.

The instance index, on the other hand, is a zero-based, four-digit sequential number that identifies a specific device instance within a device setup class. Therefore, if no other device instances exist for the given device ID at the time of creation, the instance index will be 0000.

Let's deploy the same driver as earlier using this approach.

First, we create the service in the usual sc.exe create way:


sc.exe create AwinicDriver binPath= "C:\\runtime_service\\AwinicSmartKAmps.sys" type= kernel start= demand
[SC] CreateService SUCCESS

sc.exe start AwinicDriver

SERVICE_NAME: AwinicDriver
        TYPE               : 1  KERNEL_DRIVER
        STATE              : 4  RUNNING
                                (STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
        WIN32_EXIT_CODE    : 0  (0x0)
        SERVICE_EXIT_CODE  : 0  (0x0)
        CHECKPOINT         : 0x0
        WAIT_HINT          : 0x0
        PID                : 0
        FLAGS

Then, we run the installer (full source code of create_sd_bind_driver.c can be found here):


create_sd_bind_driver.exe FAKEHWID_54321 AwinicDriver {62f9c741-b25a-46ce-b54c-9bccce08b6f2}
Device node created: ROOT\SOFTWAREDEVICE\0000 (DevInst=2)
Service bound: AwinicDriver
Class key created: SYSTEM\CurrentControlSet\Control\Class\{62f9c741-b25a-46ce-b54c-9bccce08b6f2}\0003
Driver value set: {62f9c741-b25a-46ce-b54c-9bccce08b6f2}\0003
ConfigFlags set: 0
Restarting device (disable/enable cycle)...
Device enabled. AddDevice should have been called.

Here are the relevant registry structures after running create_sd_bind_driver.exe:

HLLM\SYSTEM\CurrentControlSet\Control\Class\{GUID}
HKLM\SYSTEM\CurrentControlSet\Enum\ROOT\SOFTWAREDEVICE\0000

By inspecting the device again in the Device Manager, we can clearly see that: - the device was created, - the driver got installed for it, - the device stack got created.

Device working properly
Arbitrary hardware ID
Device stack built

If we look into the Driver tab, we will notice that the driver is "not digitally signed", which can be a bit misleading:

Driver not digitally signed

The reason we are seeing this is because there is no INF file and no CAT file tied to this driver in the registry.

The .sys file itself is digitally signed, and of course test signing was not enabled at the time of testing:

Digital signature valid, no test signing

This demonstrates that INF files and their signatures are not a security boundary, but rather a feature meant to prevent INF file abuse (e.g. threat actors backdooring INF files by adding malicious directives, which would then execute with administrative privileges during installation).

That makes sense - after all, we did not need INF files to append drivers to the UpperFilters/LowerFilters keys. And we don't really need them to tie a driver to a piece of hardware.

The same forced installation approach can be performed for already existing devices. We deploy the driver with sc.exe, modify the registry to tie it as the driver for that device, then restart the device.

Alternatively, we could even replace the relevant .sys file and restart the device, however that would not work if we are not able to unload the original driver before replacement (because the kernel will not load another module with the same name).

4.5 Working around active hardware probing

Passing various hardware probing checks mentioned in section 3.4 Active hardware interaction and probing without the corresponding physical hardware is for the most part possible, but requires either custom kernel mode code (drivers dedicated explicitly for this purpose, especially bus and filter drivers) or hypervisor access.

None of these requirements are satisfied in a typical BYOVD scenario. A thorough analysis of hardware interaction mechanisms and the feasibility of spoofing them from user-mode alone is beyond the scope of this article; however, my preliminary assessment suggests that no purely userland technique can generically bypass hardware-gated code paths without loading additional kernel mode components.

5. Final thoughts

While critical vulnerabilities are still quite common in Windows kernel mode drivers, not all drivers with relevant vulnerabilities existing in their code have practical value from the BYOVD perspective due to conditional (usually hardware-dependant) reachability of vulnerable code paths.

As I have demonstrated, many of those cases can be worked around by influencing the internal driver state from user-mode by creating software-emulated device nodes with spoofed hardware IDs or forced replacement of drivers for existing hardware.

Understanding these caveats can help better assess risks associated with specific device driver vulnerabilities.

It is reasonable to assume that at some point in the near future the range of BYOVD-viable drivers will start shrinking due to AI-accelerated vulnerability research and Microsoft removing trust for cross-signed drivers, just to name a few reasons.

Once that starts happening, it may give threat actors the incentive to start taking advantage of vulnerable drivers whose exploitability only seemingly depends on the presence of the corresponding physical hardware, while in practice can be worked around by solely operating from userland. Therefore, it is worth for defenders to pay attention to the forensic footprint associated with the techniques demonstrated in this article.

If you want to read similar articles or the predecessor of this article called "Anatomy of Access: device objects from a security perspective", Click here.

Note: This article was thoughtfully written and contributed for our audience Julian Horoszkiewicz, Atos Threat Research Center.

Found this article interesting? This article is a contributed piece from one of our valued partners. Follow us on Google News, Twitter and LinkedIn to read more exclusive content we post.



from The Hacker News https://ift.tt/eIr30qJ
via IFTTT