It’s no secret that macOS post-exploitation is often centric around targeting the installed apps for privilege escalation, persistence and more. Indeed, we’ve previously posted about approaches for code injection in macOS apps in the past and would recommend a refresher if you’re unfamiliar with these techniques.
On a recent red team engagement, we were exploring the endpoint of a compromised engineer looking for opportunities to elevate. One of the apps the user was making heavy use of was VSCode which led to further research in to avenues to obtain code execution in the context of the app. As a supported means of code execution, perhaps the most obvious way to achieve this was through a “malicious” VSCode extension.
This post will cover how to create a malicious VSCode extension on macOS that can be used for further post-exploitation shenanigans.
VSCode supports a multitude of languages, but given it’s an Electron app we opted to create our backdoor extension using Node.
npm install -g yo generator-code
Once the modules are installed, run “
My favourite IDE for developing VSCode backdoors is VSCode, open it up with “
code mdsecbackdoor” and navigate to the
package.json file. Inside this file, you will discover a activationEvents configuration parameter. This parameter defines the conditions on when the extension will execute and by default it is set to run when the relevant VSCode command is executed; modify this to use a wildcard so it executes when VSCode opens as shown below:
"activationEvents": [ "*" ],
At this point, when VSCode opens, your extension will execute the
activate() function inside the
extension.js file; the template provides a “helloworld” example, which you can remove.
Around the same time as working on this, I noticed the following message on the #mythic Slack channel from @antman1P which showed JXA trivially being executed inside an Electron app:
This feature was being achieve through the Node osascript module and the timing could not have been better. To test whether this would work inside a VScode extension, install the
osascript module using in your extension folder:
npm install osascript --save
Executing JXA from inside your extension is relatively straightforward using the
Popup dialogues are a common occurrence on macOS and it is no secret that macOS users are highly trusting of these; as an example of our nefarious post-ex, let’s pop up a credential dialogue with the following code:
You can test your extension from inside VSCode by hitting F5, at which point a Extension Development copy of VSCode should load and your dialogue prompt be presented:
This of course can also be leveraged as a persistence technique should you wish your extension to execute Mythic stager JXA.
In order to deploy this to a compromised endpoint, we now need to package it up. In order to do this, you need to edit the package.json file and add a UUID for a publisher. For our case, any will do and you can generate one with python using
python -c 'import uuid; print(uuid.uuid4());':
To package the extension, you can use the Node vsce module, install it then run the package command inside your extension folder:
npm install -g vsce vsce package
This will then create a vsix file which you can upload to the compromised endpoint:
From the endpoint, the extension can then be deployed as follows:
The next time VSCode is opened, your extension will execute. If you’re using a credential popper, you probably don’t want this to run every time so you can remove the extension simply by removing the associated folder from the user’s
If we dive in to how the Node
osascript module is working, you will quite quickly get an idea of what it’s doing by looking what happens when you call eval, from
As you can guess, looking at the getSpawn() function, the module is simply calling “
osascript” on the command line to execute the JXA:
We can confirm this by checking a process listing when opening VSCode:
With this in mind, there’s a few potential ways to detect this technique:
--install-extensionarguments. This of course is likely to be prone to false positives, but may provide an initial thread for hunting with combined with other telemetry.
osascript” binary as a child process of VSCode, as shown in the process tree below:
This blog post was written by Dominic Chell.