Resumen ejecutivo

Slow Pisces (también conocido como Jade Sleet, TraderTraitor, PUKCHONG) es un actor estado-nación de Corea del Norte enfocado principalmente en generar ingresos para el régimen de la RPDC, generalmente atacando a grandes organizaciones en el sector de las criptomonedas. En este artículo se analiza su campaña, que creemos está relacionada con los recientes robos de criptomonedas.

En esta campaña, Slow Pisces se puso en contacto con desarrolladores de criptomonedas en LinkedIn, haciéndose pasar por posibles empleadores y enviando malware camuflado en los retos de código. Estos retos requieren que los desarrolladores ejecuten un proyecto comprometido, infectando sus sistemas con un malware que hemos denominado RN Loader y RN Stealer.

Al parecer, el grupo robó más de 1000 millones de dólares del sector de las criptomonedas en 2023. Lo han logrado utilizando varios métodos, entre ellos aplicaciones comerciales falsas, malware distribuido a través del Administrador de paquetes de Node (NPM) y compromisos de la cadena de suministro.

En Diciembre de 2024, el FBI atribuyó a Slow Pisces el robo de 308 millones de dólares de una empresa de criptomonedas con sede en Japón. Más recientemente, el grupo apareció en los titulares por su presunta implicación en el robo de 1500 millones de dólares de una bolsa de criptomonedas de Dubai.

Hemos compartido nuestra inteligencia de amenazas con los analistas de GitHub y LinkedIn para deshabilitar con las cuentas y repositorios relevantes.

En respuesta, brindaron la siguiente declaración:

GitHub y LinkedIn eliminaron estas cuentas maliciosas por violar nuestros respectivos términos de servicio. En todos nuestros productos utilizamos tecnología automatizada, combinada con equipos de expertos en investigación e informes de los miembros, para combatir a los actores maliciosos y hacer cumplir las condiciones de servicio. Seguimos evolucionando y mejorando nuestros procesos y animamos a nuestros clientes y afiliados a informar de cualquier actividad sospechosa.

Información adicional

En este informe se detalla cómo Slow Pisces oculta el malware dentro de sus desafíos de código y describe las herramientas posteriores del grupo, con el objetivo de proporcionar a la industria en general un mejor entendimiento de esta amenaza.

Los clientes de Palo Alto Networks están mejor protegidos frente a las amenazas comentadas en este artículo a través de nuestras suscripciones a Firewall de nueva generación con Advanced URL Filtering y Advanced DNS Security.

Si cree que su seguridad podría haber sido comprometida o si tiene un asunto urgente, póngase en contacto con el equipo de respuesta a incidentes de Unit 42.

Temas relacionados con Unit 42 Cryptocurrency, DPRK

Análisis técnico

Nuestra visibilidad de esta campaña sigue a grandes rasgos tres pasos, ilustrados a continuación en la Figura 1.

Diagrama que ilustra las amenazas de ciberseguridad que implican señuelos PDF, repositorios GitHub y un servidor C2. Muestra: 1) archivos PDF como descripciones de trabajo y hojas de preguntas que actúan como señuelos, 2) repositorios GitHub JavaScript y Python con múltiples API externas, que potencialmente obtienen datos maliciosos, y 3) un servidor C2 configurado para enviar datos benignos o una carga maliciosa bajo ciertas condiciones. Se incluyen los logotipos de Palo Alto Networks y UNIT 42.
Figura 1. Panorama de la campaña "Desafíos de codificación" de Slow Pisces.

Fase 1 - Señuelos PDF

Slow Pisces comenzó suplantando la identidad de reclutadores en LinkedIn e interactuando con objetivos potenciales, enviándoles un PDF benigno con una descripción del trabajo, como se muestra a continuación en la Figura 2. Si los objetivos potenciales lo solicitaban, los atacantes les presentaban un reto de código consistente en varias tareas descritas en una hoja de preguntas.

Imagen que muestra dos documentos uno al lado del otro. A la izquierda, la «Descripción del puesto» de coordinador de un equipo de diseño UX. A la derecha, una «Hoja de preguntas» con cuestiones técnicas y generales relacionadas con el diseño de la experiencia del usuario (UX).
Figura 2. Señuelos PDF benignos.

Hemos observado cómo Slow Pisces se hacía pasar por varias organizaciones con estos señuelos, principalmente en el sector de las criptomonedas. Las hojas de preguntas incluyen tareas genéricas de desarrollo de software y un reto de código de un "proyecto real", que enlaza con un repositorio de GitHub que se muestra en la Figura 3 a continuación.

Captura de pantalla de un documento titulado «Habilidades de codificación y resolución de problemas con un proyecto real». Incluye un enlace a un repositorio de GitHub y describe una tarea de codificación relacionada con los tipos de cambio de Bitcoin y Ethereum a partir de fuentes API. El texto pide que se mejore el proyecto añadiendo más API de mercado y mejorando la comunicación de red en el código.
Figura 3. "Proyecto real" desafío de codificación contenido en el señuelo PDF.

Fase 2 - Repositorios de GitHub

Slow Pisces presentó a los objetivos los denominados retos de código como proyectos en repositorios de GitHub. Los repositorios contenían código adaptado de proyectos de código abierto, incluidas aplicaciones para visualizar y analizar:

  • Datos bursátiles
  • Estadísticas de las ligas de fútbol europeas
  • Datos meteorológicos
  • Precios de las criptomonedas

El grupo utilizó principalmente proyectos en Python o JavaScript, probablemente en función de si el objetivo solicitaba un puesto de desarrollo “front-end” o “back-end”. También vimos repositorios basados en Java en esta campaña, aunque fueron mucho menos comunes, con solo dos instancias suplantando una aplicación de criptomoneda llamada jCoin.

Esta escasez indica que los atacantes podrían haber creado repositorios bajo demanda, basándose en el lenguaje de programación preferido del objetivo. En consecuencia, el grupo utilizó con mayor frecuencia lenguajes más populares en el sector de las criptomonedas, como JavaScript y Python. Del mismo modo, es posible que también existan repositorios sin descubrir para otros lenguajes de programación.

Fase 3a - Repositorio Python

A finales de 2024, el grupo utilizó un proyecto que se muestra a continuación en la Figura 4 titulado "Analizador de patrones de existencias" adaptado de un repositorio legítimo.

Captura de pantalla de un repositorio de GitHub denominado "Stocks Pattern Analyzer" que muestra la estructura de archivos a la izquierda y el contenido del archivo README a la derecha, en el que se explica cómo ejecutar la aplicación directamente y con Docker.
Figura 4. "Analizador de patrones de existencias" Repositorio Python.

La mayor parte del código del repositorio es benigno. Cuando los objetivos intentan ejecutar el proyecto según la hoja de preguntas, los datos se obtienen de tres ubicaciones remotas:

  • hxxps://en.wikipedia[.]org/wiki/List_of_S%26P_500_companies
  • hxxps://en.wikipedia[.]org/wiki/Currency_pair
  • hxxps://en.stockslab[.]org/symbols/sp500

Dos de las URL extraen datos de Wikipedia. La tercera URL utiliza un dominio controlado por Slow Pisces. Este patrón (utilizar múltiples fuentes de datos, la mayoría legítimas, pero una maliciosa) es común en los repositorios Python del grupo.

El servidor de mando y control (C2) malicioso está configurado para imitar el formato de las fuentes legítimas. En este caso, utiliza el subdominio en y el dominio de nivel superior (TLD) org como vemos arriba para el dominio legítimo de Wikipedia.

Deserialización de YAML

Slow Pisces podría simplemente colocar malware directamente en el repositorio o ejecutar código desde el servidor C2 utilizando las funciones eval o exec integradas en Python. Sin embargo, estas técnicas son fácilmente detectables, tanto por inspección manual como por soluciones antivirus.

En su lugar, Slow Pisces se asegura primero de que el servidor C2 responde con datos de aplicación válidos. Por ejemplo, el repositorio mencionado anteriormente espera una lista de símbolos de empresas del S&P 500. La URL C2 responde inicialmente con estos datos en una lista con formato JSON.

Los actores sólo envían una carga maliciosa a objetivos validados, probablemente basándose en la dirección IP, la geolocalización, la hora y los encabezados de las solicitudes HTTP. El hecho de centrarse en las personas contactadas a través de LinkedIn, a diferencia de las amplias campañas de phishing, permite al grupo controlar estrictamente las últimas fases de la campaña y entregar las cargas únicamente a las víctimas esperadas.

Para evitar las sospechosas funciones eval y exec , Slow Pisces utiliza deserialización YAML para ejecutar su carga, como se muestra en la Figura 5.

Captura de pantalla de código Python que define una función “fetch_symbols” que recupera símbolos bursátiles del S&P 500 mediante una llamada API, gestiona diferentes tipos de contenido y procesa las respuestas en función de su tipo de contenido. La última línea tiene una sección resaltada en un recuadro rojo.
Figura 5. Código Python que muestra el punto de entrada del malware de Slow Pisces que utiliza la deserialización YAML.

Este código obtiene datos del servidor C2 a través de HTTPS y comprueba la cabecera de respuesta Content-Type. Si la cabecera indica datos JSON (application/json), el código analiza y devuelve el JSON a la aplicación.

Si la respuesta indica datos YAML (application/yaml), el código utiliza la función yaml.load() de la biblioteca PyYAML para analizar los datos. Esta función es intrínsecamente insegura y la documentación de PyYAML recomienda explícitamente yaml.safe_load() para entradas no fiables.

YAML se utiliza normalmente para archivos de configuración, como el ejemplo que se muestra a continuación:

Sin embargo, yaml.load() puede serializar y deserializar objetos Python arbitrarios, no solo datos YAML válidos. Por ejemplo, el siguiente código Python imprime los números 0-4:

Si este código se serializara utilizando yaml.dump() se convertiría en lo siguiente:

Finalmente, cuando estos datos se pasen a yaml.load() se ejecutará el código original: range(0, 5).

Esto pone de manifiesto un posible punto de detección, ya que los contenidos del repositorio Python, y el malware que utiliza la deserialización YAML en general, contienen !!python/object/apply:builtins si el contenido utiliza una función Python built-in.

Las siguientes fases en la Tabla 1 existen principalmente en memoria y, por lo general, no dejan huella en el disco. Para ayudar a la comunidad en la detección y concienciación, hemos subido estas muestras a VirusTotal. La muestra de deserialización YAML ejecuta el malware que hemos denominado RN Loader y RN Stealer basándonos en el formato de token C2 que observamos en RN Stealer, del que hablamos en las siguientes secciones.

Fase SHA256 Hash
Muestra de deserialización YAML 47e997b85ed3f51d2b1d37a6a61ae72185d9ceaf519e2fdb53bf7e761b7bc08f
RN Loader 937c533bddb8bbcd908b62f2bf48e5bc11160505df20fea91d9600d999eafa79
RN Stealer e89bf606fbed8f68127934758726bbb5e68e751427f3bcad3ddf883cb2b50fc7

Tabla 1. Muestras del repositorio Python.

La muestra de deserialización YAML de Slow Pisces comienza creando la carpeta Public en el directorio personal de la víctima y creando un nuevo archivo en ese directorio llamado __init__.py. Los datos Base64 incrustados se descodifican y se escriben en este archivo, que contiene la siguiente fase de infección (RN Loader), que se ejecuta a continuación.

RN Loader

Este archivo recién creado para el RN Loader en ~/Public/__init__.py se borra a sí mismo después de la ejecución, asegurando que existe únicamente en memoria. Envía información básica sobre la máquina víctima y el sistema operativo a través de HTTPS al mismo C2 en en.stockslab[.]org, seguido de un bucle de comandos con las siguientes opciones en la Tabla 2.

Código Descripción
0 Suspender durante 20 segundos
1 Decodifica en base64 el contenido enviado y lo guarda en el archivo init.dll para Windows o init para el resto de sistemas operativos.

Establece una variable de entorno X_DATABASE_NAME a una cadena vacía.

Carga y ejecuta la DLL descargada utilizando ctypes.cdll.LoadLibrary.

2 Decodifica en base64 el contenido enviado y lo ejecuta utilizando  la función exec incorporada en Python.
3 Decodifica en base64 el contenido enviado y un parámetro. El contenido se guarda en el archivo dockerd, mientras que el parámetro se guarda como docker-init.

dockerd se ejecuta entonces en un nuevo proceso, con docker-init suministrado como argumento de línea de comandos.

9 Finaliza la ejecución.

Tabla 2. Tabla de comandos del cargador RN.

Las cargas útlies del bucle de comandos de la Tabla 2 que utilizan las opciones 1 y 3 son actualmente desconocidas y es probable que se activen por condiciones específicas. Sin embargo, hemos recuperado un “infostealer”basado en Python entregado por la opción 2, y rastreamos este malware como RN Stealer.

RN Stealer

El RN Stealer genera primero un ID aleatorio de la víctima, utilizado posteriormente como cookie en todas las comunicaciones con el servidor C2. A continuación, solicita una clave XOR al servidor para cifrar los datos exfiltrados.

La comunicación con el servidor C2 se produce a través de HTTPS, utilizando tokens codificados en Base64 para identificar los tipos de solicitud y respuesta. La carga analizada incluye cuatro tipos de fichas:

  • R0 - solicitar la clave XOR
  • R64 - exfiltrar datos
  • R128 - exfiltrar datos comprimidos
  • R256 - Infostealercompleto

El formato de estos tipos de token - la letra R seguida de un número entero N - nos llevó a nombrar esta carga. Llamamos a la carga RN Stealer y a la fase precedente RN Loader.

Hemos recuperado el script de esta muestra de RN Stealer de un sistema macOS. Como tal, los autores de la amenaza adaptaron esta muestra para robar información específica de los dispositivos macOS, incluyendo:

  • Información básica sobre la víctima: Nombre de usuario, nombre de la máquina y arquitectura
  • Aplicaciones instaladas
  • Un listado de directorios y el contenido de nivel superior del directorio de usuario de la víctima
  • El archivo login.keychain-db que almacena las credenciales guardadas en los sistemas macOS.
  • Claves SSH almacenadas
  • Archivos de configuración para AWS, Kubernetes y Google Cloud

Los datos recopilados por el RN Stealer probablemente determinan si es necesario un acceso persistente. Si es así, podemos deducir los siguientes pasos para esta cadena de infección de Python:

  1. El servidor C2 comprueba las víctimas que llaman según criterios desconocidos. Las víctimas válidas reciben una carga útil de deserialización YAML. Las víctimas no válidas reciben datos JSON benignos.
  2. La carga de deserialización establece un bucle de comandos con el servidor C2, exfiltrando información básica de la víctima y entregando un infostealera medida en Python a través de la opción 2 de código en la Tabla 2.
  3. El infostealer recopila información más detallada de la víctima, que los atacantes probablemente utilizaron para determinar si necesitaban acceso continuado.
    1. Si se requiere un acceso continuado, el servidor C2 entrega una carga mediante las opciones 1 o 3 de código
    2. Si el acceso ya no es necesario, la opción 9 de código termina la ejecución del malware, eliminando todo acceso ya que la carga reside únicamente en la memoria.

Fase 3b - Repositorio JavaScript

Si las víctimas objetivo solicitaron una posición de JavaScript, podrían encontrarse en su lugar con un proyecto de "Cuadro de mando de criptomoneda", similar al ejemplo de la Figura 6 a continuación.

Captura de pantalla de un repositorio de GitHub llamado "Cryptocurrency Dashboard", en el que aparece un archivo README.md. Este README incluye secciones: Características, Instalación, Uso, Estructura del proyecto, Configuración, Dependencias y Licencia. Describe el proyecto como una aplicación construida con Node.js, Express y EJS que muestra datos históricos y en tiempo real de varias criptomonedas.
Figura 6. Repositorio JavaScript.

Esta aplicación contiene un archivo .env con el C2 y la fuente de datos legítima:

  • PORT=3000
  • COINGECKO_API_URL=hxxps://api.coingecko[.]com/api/v3
  • JQUERY_API_URL=hxxps://update.jquerycloud[.]io/api/v1

El valor COINGECKO_API_URL se utiliza para obtener datos para el panel de criptomonedas, mientras que el valor JQUERY_API_URL representa un servidor C2 controlado por Slow Pisces. De forma similar al repositorio Python, el servidor C2 JavaScript solo entrega cargas a objetivos validados, de lo contrario, responde con un número de versión.

El repositorio utiliza la herramienta de creación de plantillas Embedded JavaScript (EJS), pasando las respuestas del servidor C2 a la función ejs.render(), que se muestra a continuación en la figura 7.

Captura de pantalla que muestra un fragmento de código en JavaScript. Incluye un comentario y una llamada a una función para renderizar una página de inicio con ajustes y elementos por página. res.render aparece resaltado en un recuadro rojo
Figura 7. Código JavaScript que muestra el punto de entrada del malware de Slow Pisces utilizando la función de renderizado EJS.

Al igual que el uso de yaml.load(), esta es otra técnica que emplea Slow Pisces para ocultar la ejecución de código arbitrario desde sus servidores C2, y este método quizás solo sea aparente cuando se visualiza una carga válida.

La función de renderizado de EJS acepta varios parámetros, uno de los cuales se denomina “view options”. Dentro de este, se puede suministrar y ejecutar código JavaScript arbitrario a través de la clave escapeFunction.

Un investigador taiwanés que se hace llamar “Huli” discutió los detalles técnicos de cómo esto resulta en la ejecución de código arbitrario en una publicación de CTF. Sin embargo, podemos entender suficientemente que una carga estructurada como se muestra en la Figura 8 dará lugar a que el código contenido en escapeFunction se ejecute cuando se pase a ejs.render().

Captura de pantalla de un fragmento de código JavaScript que incluye funciones con "escapeFunction" resaltado en un recuadro rojo.
Figura 8. Carga de renderizado EJS parcial.

Lamentablemente, no pudimos recuperar la totalidad de esta carga. Como tal, solo podemos suponer que se crea un nuevo directorio .jql bajo el directorio personal del usuario donde se suelta un archivo llamado helper.js, que contiene datos codificados en Base64.

Infraestructura

La línea de tiempo que aparece a continuación en la Figura 9 detalla la infraestructura C2 utilizada en esta campaña desde febrero de 2024 a febrero de 2025, agrupada por el tipo de repositorio servido (JavaScript o Python).

Cronología de la infraestructura de seguimiento del comando y los controles de JavaScript (arriba, etiqueta amarilla) y del comando y los controles de Python (abajo, etiqueta naranja). La cronología comienza a finales del primer trimestre de 2024 y continúa hasta el segundo trimestre de 2025.
Figura 9. Cronología de la infraestructura C2

Como ya se ha mencionado, los dominios de la infraestructura de esta campaña pueden imitar el formato de las fuentes legítimas utilizadas junto a ellos, utilizando con frecuencia subdominios como api o cdn. Hemos descubierto infraestructura asociadas a esta campaña hasta el momento de redactar este artículo.

Conclusión

Este informe ha cubierto la campaña más reciente de Slow Pisces, que se hizo pasar por reclutadores a través de LinkedIn para dirigirse a desarrolladores en el sector de las criptomonedas con retos de código maliciosos. Si bien no pudimos recuperar la cadena de ataque completa para los repositorios JavaScript, la versión Python de la campaña entregó dos nuevas cargas que hemos denominado RN Loader y RN Stealer.

Utilizar LinkedIn y GitHub de esta manera no es algo extraño. Múltiples grupos afiliados a la RPDC han utilizado tácticas similares como Alluring Pisces y Contagious Interview.

Estos grupos no presentan solapamientos operativos. Sin embargo, cabe destacar que estas campañas utilizan vectores de infección iniciales similares.

Slow Pisces destaca entre las campañas de sus compañeros en seguridad operativa. La entrega de las cargas en cada fase está fuertemente vigilada, existiendo solo en memoria. Y las herramientas de la fase posterior del grupo solo se implementan cuando es necesario.

En concreto, el grupo hizo uso de dos técnicas para ocultar la funcionalidad:

  • Deserialización de YAML
  • EJS escapeFunction

Ambas técnicas dificultan enormemente el análisis, la detección y la búsqueda. Del mismo modo, los desarrolladores relativamente nuevos o inexpertos en el sector de las criptomonedas tendrían dificultades para identificar estos repositorios como maliciosos.

Basándose en los informes públicos de robos de criptomonedas, esta campaña parece tener mucho éxito y es probable que persista en 2025. Si bien este artículo destacaba dos oportunidades potenciales de detección para la deserialización YAML y las cargas EJS escapeFunction, la mitigación más eficaz sigue siendo la segregación estricta de los dispositivos corporativos y personales. Esto ayuda a prevenir el compromiso de los sistemas corporativos a partir de campañas de ingeniería social dirigidas.

Protección y mitigación de Palo Alto Networks

Los clientes de Palo Alto Networks están mejor protegidos frente a las amenazas mencionadas gracias a los siguientes productos:

Si cree que su seguridad puede haber sido comprometida o si tiene un asunto urgente, póngase en contacto con el equipo de respuesta a incidentes de Unit 42 o llame al:

  • América del Norte: Llamada gratuita: +1 (866) 486-4842 (866.4.UNIT42)
  • Reino Unido: +44.20.3743.3660
  • Europa y Oriente Medio: +31.20.299.3130
  • Asia: +65.6983.8730
  • Japón: +81.50.1790.0200
  • Australia: +61.2.4062.7950
  • India: 00080005045107

Palo Alto Networks ha compartido estos hallazgos con nuestros colegas de la Cyber Threat Alliance (CTA). Los miembros de la CTA utilizan esta inteligencia para implementar rápidamente protecciones a sus clientes y perturbar sistemáticamente a los ciberactores maliciosos. Obtenga más información sobre la Cyber Threat Alliance.

Indicadores de Compromiso

Dominio Dirección IP Visto por primera vez Visto por última vez Repositorio
getstockprice[.]com 70.34.245[.]118 2025-02-03 2025-02-20 Python
cdn[.]clubinfo[.]io 5.206.227[.]51 2025-01-21 2025-02-19 Python
getstockprice[.]info 131.226.2[.]120 2025-01-21 2025-01-23 Python
api[.]stockinfo[.]io 136.244.93[.]248 2024-10-30 2024-11-11 Python
cdn[.]logoeye[.]net 54.39.83[.]151 2024-10-29 2024-11-03 Python
en[.]wfinance[.]org 195.133.26[.]32 2024-10-12 2024-11-01 Python
en[.]stocksindex[.]org 185.236.231[.]224 2024-09-11 2024-10-04 Python
cdn[.]jqueryversion[.]net 194.11.226[.]16 2024-08-23 2024-09-23 JavaScript
en[.]stockslab[.]org 91.103.140[.]191 2024-08-19 2024-09-12 Python
update[.]jquerycloud[.]io 192.236.199[.]57 2024-07-03 2024-08-22 JavaScript
cdn[.]soccerlab[.]io 146.70.124[.]70 2024-08-07 2024-08-21 Python
api[.]coinpricehub[.]io 45.141.58[.]40 2024-05-06 2024-08-06 Java
cdn[.]leaguehub[.]net 5.133.9[.]252 2024-07-15 2024-07-21 Python
cdn[.]clublogos[.]io 146.19.173[.]29 2024-06-24 2024-07-12 Python
api[.]jquery-release[.]com 146.70.125[.]120 2024-06-10 2024-06-28 JavaScript
cdn[.]logosports[.]net 185.62.58[.]74 2024-05-08 2024-06-23 Python
skypredict[.]org 80.82.77[.]80 2024-05-06 2024-06-16 JavaScript
api[.]bitzone[.]io 192.248.145[.]210 2024-04-25 2024-05-13 Python
weatherdatahub[.]org 194.15.112[.]200 2024-04-05 2024-05-03 JavaScript
api[.]ethzone[.]io 91.234.199[.]90 2024-04-16 2024-04-24 Python
api[.]fivebit[.]io 185.216.144[.]41 2024-04-08 2024-04-14 Python
blockprices[.]io 91.193.18[.]201 2024-03-15 2024-04-09 JavaScript
api[.]coinhar[.]io 185.62.58[.]122 2024-03-26 2024-04-09 Python
mavenradar[.]com 23.254.230[.]253 2024-02-21 2024-03-26 JavaScript
indobit[.]io 146.70.88[.]126 2024-03-19 2024-03-20 Python
api[.]thaibit[.]io 79.137.248[.]193 2024-03-07 2024-03-09 Python
chainanalyser[.]com 38.180.62[.]135 2024-02-23 2024-03-06 JavaScript

Recursos adicionales

 

Enlarged Image