March 18, 2025 By Bobby Cooke 10 min read

Windows Defender Application Control (WDAC) is a security solution that restricts execution to trusted software. Since it is classified as a security boundary, Microsoft offers bug bounty payouts for qualifying bypasses, making it an active and competitive field of research.

Typical outcomes of a WDAC bypass bug bounty submission:

  • Bypass is fixed; possible bounty awarded
  • Bypass is not fixed but instead “mitigated” by being added to the WDAC recommended block list. Likely no bounty awarded but honorable mention is typically given
  • Bypass is not fixed, no bounty awarded, no honorary mention

Looking at Microsoft’s WDAC recommended block list we see legends like Jimmy Bayne (@bohops) and Casey Smith (@subTee) have discovered WDAC bypasses that remain unfixed but have been given honorary mentions. Looking beyond this list, the LOLBAS Project contains additional unfixed bypasses that have not been acknowledged in Microsoft’s block list. One example is the Microsoft Teams application, which remains a viable WDAC bypass despite being documented in LOLBAS.

When encountering WDAC during Red Team Operations, we successfully bypassed it and executed our Stage 2 Command and Control (C2) payload using the following techniques:

  1. Use a known LOLBIN like MSBuild.exe
    • Works if the client has not implemented the recommended block list rules.
    • Many EDR solutions with “100% MITRE Coverage” have detections for these well-known LOLBINs.
  2. DLL side-load a trusted application with an untrusted DLL
    • Effective if WDAC is enabled but not enforcing DLL signing.
  3. Exploit custom exclusion rule from the client’s WDAC policy
  4. Find a new execution chain in a trusted application that allows C2 deployment

Electron applications

As Ruben Boonen (@FuzzySec) explained in his Wild West Hackin’ Fest talk Statikk Shiv: Leveraging Electron Applications for Post-Exploitation, Electron applications function as web browsers that render desktop applications using standard web technologies like HTML, JavaScript and CSS. The JavaScript engine in Electron is Node.js, which provides powerful APIs capable of interacting with the host operating system. These APIs allow actions such as reading and writing files, executing programs and other operations typical of native applications.

How Electron applications execute Node.js JavaScript

At runtime, an Electron application reads JavaScript files, interprets their code and executes them within the Electron process. The animation below demonstrates how the Microsoft Teams Electron application reads a JavaScript file at runtime, which then uses the child_process module to execute whoami.exe.

In this example, the Teams Electron process reads the JavaScript file, which then spawns whoami.exe using the child_process module. This module triggers the Electron process to execute its exported API uv_spawn, responsible for interacting with the operating system to create a new process.

Typical Windows applications vs. Electron applications

The traditional architecture of a Windows application consists of:

  • An Executable (EXE) that runs the main program logic.
  • Dynamic Link Libraries (DLLs) that provide additional functionality.

The EXE calls exported functions from DLLs to extend its capabilities. However, Electron applications reverse this architecture. Instead of the EXE calling APIs from DLLs, the Electron EXE itself exposes API exports, which are called by:

  • Node.js JavaScript files
  • Node modules (which function as DLLs)

This structure allows Node.js JavaScript and Node modules to interact with the operating system in ways that traditional JavaScript in a browser cannot.

In the image below we examine the Teams Electron application using PE Bear, an awesome tool created by @hasherezade, which reveals that the Teams Electron executable contains 2,977 exported APIs. This large API surface provides extensive functionality that can be leveraged by Node.js JavaScript files and Node modules to interact with the operating system.

Executing arbitrary JavaScript code with signed Electron applications

Since Electron applications execute JavaScript at runtime, modifying these JavaScript files allows attackers to inject arbitrary Node.js code into the Electron process. By leveraging Node.js and Chromium APIs, JavaScript code can interact with the operating system.

The ability to modify JavaScript files of trusted Electron applications to execute arbitrary Node.js JavaScript code was not discovered by me. The earliest references I can find date back to 2022.

At the start of 2022, Andrew Kisliakov published the blog “Microsoft Teams and other Electron Apps as LOLbins”. Andrew and @mrd0x contributed their findings to the LOLBAS project.

Later in 2022, Valentina Palmiotti (@chompie1337), Ellis Springe (@knavesec), and Ruben further explored this approach, leading to the development of an internal persistence tool that has since been used in red team operations.

Also in 2022, Michael Taggart released the quASAR project, a tool designed for modifying Electron applications to enable command execution. In his blog “Quasar: Compromising Electron Apps”, he shared that in September 2022, a member of the Electron project reached out to him, stating that integrity checking was an experimental feature and would hopefully be fully supported in the future.

From personally experimenting with newer Electron applications like Signal, I have confirmed that integrity checks are now in place for some Electron applications which prevent their JavaScript files from being modified. Still, many actively distributed Electron applications remain vulnerable.

This technique has also been observed in real-world attacks. In 2022, a threat actor backdoored the MiMi chat application by modifying its bundled JavaScript files on the distribution server. Trend Micro identified this as a supply-chain attack, where the compromised Electron app was distributed to end users, enabling the execution of malicious JavaScript code which downloaded and executed a second stage C2 payload.

Hunting for WDAC bypasses

In April of 2024, I Bobby Cooke (@0xBoku) was hunting for a new execution chain to use in preparation for an upcoming Red Team Operation for a client in the financial sector. This sector has higher security standards and stricter regulations, often implementing additional security controls like WDAC. During my research, I came across another vulnerable Electron application. However, since it wasn’t signed by Microsoft, it was unlikely to bypass the client’s WDAC policy.

Reexploring Teams attack surface

I then pivoted to the legacy Microsoft Teams application, which is signed by Microsoft and can bypass even the strictest WDAC policies. At this point, Dylan Tran (@d_tranman) joined me on this quest, and we began hunting for a way to escalate from arbitrary Node.js JavaScript execution to executing our stage 2 C2 shellcode.

While Node.js can interact with the operating system through its APIs, it lacks the full functionality of C, where developers can directly call WINAPIs and NTAPIs. To bridge this gap, developers created Node modules, which extend the capabilities of the Node.js framework. These modules, compiled from C++ code, can call WINAPIs, interact with Node.js APIs and execute JavaScript within Electron applications. Compiled Node modules have a .node extension and are loaded into Windows processes via a DLL load event.

Reversing signed node modules

During our research, we examined several Electron applications and analyzed their signed Node modules. We discovered that these modules could be interacted with directly from JavaScript, allowing us to leverage their built-in capabilities.

While creating our own custom Node modules to execute shellcode is a viable approach and is a capability of Loki C2, it presents a chicken-and-egg problem. Loading a Node module from JavaScript triggers a DLL load event, which can be blocked by WDAC policies enforcing strict rules against unsigned DLLs. Fortunately, a large number of signed Node modules exist across legitimate Electron applications.

This approach to executing our payload looked promising, so we shared our findings with Valentina and she joined us on this quest. With her help, we dove deeper into reversing signed Node modules, searching for vulnerabilities or built-in capabilities that would allow us to execute arbitrary shellcode.

Leveraging capabilities of signed node modules

One example of a Node module with useful capabilities is windows_process_tree.node, a Microsoft-signed module bundled with Visual Studios Code. When examined in PE Bear, it reveals two exported functions, as seen below.

Unlike traditional DLLs, Node modules do not list all their available functions in the export table. The napi_register_module_v1 exported function is called by the Electron process and is responsible for loading the module and exposing its exported functionality to the Electron process. This acts as a bridge, enabling JavaScript within the Electron process to call and interact with the module’s functions.

A trivial way to list all the callable functions in a Node module is to leverage the Node.js code below.

Executing this Node.js script in PowerShell, we see there are two callable functions in windows_process_tree.node. They are getProcessList and getProcessCpuUsage.

With persistence, it is possible to determine how to call these functions from JavaScript. One limitation of Node.js is that it lacks a built-in API to list all running processes on the system. This limitation is why Microsoft introduced the getProcessList function within this module, expanding the capabilities of the VS Code Electron application.

It is possible to retrieve this information directly in JavaScript by using the child_process module to execute PowerShell in a child process, which returns details about running processes. In the image below, Loki C2 spawns a PowerShell child process to retrieve the process list.

This approach presents significant operational security risks. Executing PowerShell child processes is highly detectable and increases the likelihood of an operation being flagged or burned. To avoid this, Loki C2 leverages signed Node modules like windows_process_tree.node to extend Node.js capabilities.

Loki C2 includes the ps command, which retrieves process information by loading in the windows_process_tree.node Microsoft-signed module and calling the getProcessList function, as seen in the image below.

The Loki C2 JavaScript code that calls the getProcessList function in the windows_process_tree.node module is shown below. getProcessList returns process data in JSON format, which Loki C2 formats into a structured table for improved readability.

Determining how to properly call functions within Node modules can be challenging since their internal structures are not documented. However, by using tools like Ghidra, developed by the NSA, and collaborating with skilled reverse engineers like Valentina, we have successfully analyzed these modules and identified how to interact with their functions.

Discovering our first shellcode execution technique

Valentina ultimately discovered a way to execute our Stage 2 C2 shellcode without loading an unsigned DLL, but I’ll leave it to her to disclose the details. Together, Dylan, Valentina and I worked on refining the technique to ensure stability for the upcoming phishing campaign.

Unfortunately, our initial email phishing campaign was reported and blocked by the Blue Team. After this setback, Brett Hawkins (@h4wkst3r) and I began preparing for a second campaign. As the designated payload guy, I didn’t want to reuse the same payload—doing so would have made it too easy for the Blue Team to track us and stop our second campaign. However, we didn’t have enough time to apply Valentina’s technique to a new payload, so I started developing a new payload using an alternative approach.

The Dawn of Loki C2

Typically, the ability to execute arbitrary JavaScript in trusted Electron applications is used to execute commands that deploy a C2 agent. However, without Valentina’s technique, this approach would fail against WDAC, as it would eventually require executing an unsigned program, which would likely be blocked.

With only a few days to prepare for the second campaign, I had the thought: What if I built an entire C2 framework in JavaScript?

If the C2 agent itself was entirely written in JavaScript, it could establish a C2 channel even against the strictest WDAC policies. From there, reconnaissance could be performed to find a way to deploy a stage 2 C2 payload. There would be no unsigned DLL load events—just JavaScript executing within the trusted Teams process.

All we needed was enough functionality to:

  1. Retrieve the WDAC policy from the target system
  2. Find exploitable exclusions in deployed WDAC policy
  3. Upload the stage 2 C2 payload
  4. Execute the stage 2 C2 payload

Leveraging all the Node.js code I had written during my research, I threw together a C2 proof of concept overnight. The next day, I shared it with Dylan, and together, we rapidly expanded it into a fully functional JavaScript-based C2. Our C2 was capable of:

  • Uploading & downloading files
  • Listing files & directories
  • Reading files
  • Executing commands
  • Dynamically loading Node modules for extended capabilities

The JavaScript C2, now known as Loki C2, was a success in the second campaign. Since then, we’ve continued refining and expanding Loki C2, adding more features, increasing stability and enhancing capabilities.

With all the Electron knowledge I gained from this research, I built a graphical user interface for Loki C2 using the Electron framework.

Bypassing WDAC with Loki C2 demo

Last week I posted this video on X which demonstrates bypassing a strict WDAC policy with Loki C2. The two sections below explain what is happening in the video.

WDAC policy creation & deployment

For this demo, WDAC is deployed via the App Control Policy Wizard on an up-to-date Windows Server 2025 EC2 instance in AWS. The wizard provides three base policy templates:

  1. Default Windows Mode
  2. Allow Microsoft Mode
  3. Signed and Reputable Mode

The Default Windows Mode is the strictest, allowing execution of:

  • Windows OS components
  • Microsoft Store applications
  • Office 365, OneDrive, Teams
  • WHQL-signed kernel drivers

In the demo, the Default Windows Mode policy is selected. The default Audit Mode is disabled so that WDAC enforces the policy immediately. Additionally, the Merge with recommended block lists options are checked which includes the rules in Microsoft’s Recommended WDAC Block List. The App Control Wizard generates an XML and CIP file for the WDAC policy, which is then deployed to the server using CITool.exe.

Demonstration of WDAC bypass

Once WDAC is active, I attempt to execute Loki C2 Agent.exe, but WDAC blocks it, as the executable is not signed by Microsoft.

To bypass this restriction, I copy the contents of Loki Agent’s /resources/app/ directory. A folder named “teams” is on the desktop, containing a legitimate legacy Microsoft Teams application. Viewing the properties of Teams.exe confirms that it is signed by Microsoft.

I then navigate to the /resources/ directory of the Teams application and delete all existing files. Once cleared, I paste the previously copied Loki C2 Agent /resources/app/ directory into ~/Desktop/teams/resources/app/.

With this modification, I execute Teams.exe by clicking it. Since the Teams executable is signed by Microsoft, WDAC does not block it. In System Informer, we can see the Teams process is created successfully without WDAC intervention. However, because I replaced the Teams /resources/app/ directory with Loki C2 Agent’s code, the Electron-based Teams application now executes Loki C2 Agent’s JavaScript inside the trusted Teams process.

The Teams process successfully calls back to the Loki C2 client, and I execute several commands to demonstrate remote control of the compromised server.

Upgrading to a stealthy Stage 2 C2

After gaining initial access with Loki C2, we have identified multiple ways to execute a more capable Stage 2 C2 agent, such as Dragon, the internal C2 developed by Shawn Jones (@anthemtotheego) and me. While all the different escalation methods we’ve discovered since the initial creation of Loki C2 will not be disclosed in this post, we plan to cover them in future releases.

When implemented correctly, this technique continues to bypass top-tier Endpoint Detection & Response (EDR) solutions. However, without a stealthy Stage 2 C2, operators must rely on command execution via spawn which executes commands in child processes. This will quickly trigger post-exploitation detections against leading EDRs.

MITRE ATT&CK mapping

Loki C2 aligns with MITRE ATT&CK Technique T1218.011 – System Binary Proxy Execution: Electron Applications.

After searching the internet I have not found this technique of hollowing out Electron applications and replacing their code with a C2 publicly disclosed or used in the wild. However, after sharing Loki C2 with trusted red teams, one has confirmed they have developed similar capabilities internally.

Even with a MITRE ATT&CK TTP, multiple research publications and a LOLBAS entry, this Electron application hollowing technique itself remains undetected. My assumption is that EDR solutions do not focus on detecting this but rather on post-exploitation indicators, such as spawning child processes to execute commands. Since we have developed methods to deploy Stage 2 C2 while avoiding these common post-exploitation detections, we have successfully used this technique in multiple engagements while avoiding detection.

With all this in mind, the next time you hear a vendor claiming “100% MITRE Coverage” it’s worth questioning what that really means…

More from Adversary Services

Abusing MLOps platforms to compromise ML models and enterprise data lakes

15 min read - For full details on this research, see the X-Force Red whitepaper “Disrupting the Model: Abusing MLOps Platforms to Compromise ML Models and Enterprise Data Lakes”.Machine learning operations (MLOps) platforms are used by enterprises of all sizes to develop, train, deploy and monitor large language models (LLMs) and other foundation models (FMs), as well as the generative AI (gen AI) applications built on top of these models. The rush to leverage AI throughout enterprises has meant that security has been often…

Getting “in tune” with an enterprise: Detecting Intune lateral movement

13 min read - Organizations continue to implement cloud-based services, a shift that has led to the wider adoption of hybrid identity environments that connect on-premises Active Directory with Microsoft Entra ID (formerly Azure AD). To manage devices in these hybrid identity environments, Microsoft Intune (Intune) has emerged as one of the most popular device management solutions. Since this trusted enterprise platform can easily be integrated with on-premises Active Directory devices and services, it is a prime target for attackers to abuse for conducting…

Racing round and round: The little bug that could

13 min read - The little bug that could: CVE-2024-30089 is a subtle kernel vulnerability I used to exploit a fully updated Windows 11 machine (with all Virtualization Based Security and hardware security mitigations enabled) and scored my first win at Pwn2Own this year. In this article, I outline my straightforward approach to bug hunting: picking a starting point and intuitively following a path until something catches my attention. This bug is interesting because it can be reliably triggered due to a logic error.…

Topic updates

Get email updates and stay ahead of the latest threats to the security landscape, thought leadership and research.
Subscribe today