Persistence with VSCode plugin backdoors

30. Jun 2024, by manu in posts

It turns out, Visual Studio Code does not check installed plugins for modification after they have been installed. I discovered this by accident when debugging an extension I’ve been working on. Charley Célice’s ‘Using Visual Studio Code Extensions for Persistence’↗ touches on the concept of using VSCode for persistence by installing a malicious plugin. I’m gonna take this a bit further and show you how you can achieve persistence by modifying existing plugins.

Finding the entry point #

Plugins are stored by default in $HOME/.vscode/extensions or $HOME/.vscode-oss/extensions for VSCodium. Each plugin has a folder containing which then contains a JSON file named package.json.

This file specifies general plugin information needed inside the IDE. The part we are interested in is the value of main. This is the relative path (from the plugin root) to the JavaScript app that gets executed. This can be either a path to a .js/.cjs file or a path to a directory that then contains extension.js.

Adding a backdoor #

Following the path from main, you will find a (usually minified) JavaScript file. Now all you gotta do is add the following snippet to the beginning of the JavaScript file. If the JS starts with a (, you’ll need to put it inside those parentheses. Don’t forget to escape the backslashes.


That’s all there’s to it. Once the plugin gets loaded, the command will get executed.

Disabled Plugins #

Plugins can be disabled. In this case, your backdoor will not be launched upon start. The state of the plugins can be found in the SQLite database located in $HOME/.config/<VSCODE>/User/globalStorage/state.vscdb.

Using the query below an array of id and uuid pairs will be returned. The id has the format <PUBLISHER>.<NAME> with both values taken from the package.json. If your targeted plugin is on this list, it is disabled.

SELECT value FROM ItemTable WHERE key = "extensionsIdentifiers/disabled"