Quiero que analizes como funciona el login de este sofware
[the tool could not be started: Error: MCP tools/call failed: Internal error: Error calling tool 'inspect_binary': Invalid file path]
El archivo se subió como HU6+Market+2.exe. Vamos a identificarlo:
La montura FUSE lista el archivo pero falla al leerlo. Intentaré otras formas de acceder:
[the tool could not be started: Error: MCP tools/call failed: Internal error: Error calling tool 'inspect_binary': rz-bin could not inspect the file: rz-bin -I failed: exit code -7]
El log de la montura FUSE muestra el problema: el archivo aparece en el listado pero el backend lo marca como "borrado o modificado remotamente", por lo que su contenido es ilegible. Busco si queda una copia en caché local:
No puedo iniciar el análisis todavía — hay un problema con la subida del archivo. Te explico lo que encontré:
El archivo no es legible en este momento
Evidencia:
-
El archivo aparece en el workspace con los metadatos correctos (
HU6+Market+2.exe, 3.990.457 bytes):-rwxrwxrwx 1 root root 3990457 Nov 11 21:58 HU6+Market+2.exe -
Pero cualquier intento de leer su contenido falla:
$ file "HU6+Market+2.exe" ERROR: cannot read (No such file or directory) $ head -c 16 "HU6+Market+2.exe" head: error reading 'HU6+Market+2.exe': No such file or directory -
El log del backend de almacenamiento (
/tmp/geesefs.log) lo confirma:File HU6+Market+2.exe is deleted or resized remotely, discarding local changesEs decir, el sistema de archivos ve la entrada del archivo, pero el objeto con su contenido no se recuperó correctamente al subirlo (probablemente la subida se interrumpió o quedó inconsistente).
Descarté además que quedara una copia en caché local: no hay ninguna.
Qué necesito de ti
Por favor vuelve a subir el archivo (HU6 Market 2.exe). Si puedes, súbelo de nuevo tal cual; si el problema persiste, prueba renombrarlo a algo simple sin espacios (p. ej. hu6market2.exe) antes de subirlo.
Qué haré en cuanto sea legible
Mi plan para analizar el login del software:
- Identificar la naturaleza del binario — si es .NET (análisis con decompilador C#), PE nativo, o si viene empacado (Themida/UPX/etc., habitual en software de mercado gris).
- Buscar los componentes de autenticación — strings de URLs/endpoints, campos de usuario/contraseña, hashing o cifrado aplicado a las credenciales, verificación de licencia/HWID, y dónde se guardan sesiones o tokens.
- Descompilar la rutina de login y explicarte el flujo completo: qué envía, a qué servidor, cómo valida la respuesta y qué guarda localmente.
En cuanto el archivo se lea correctamente, arranco con el paso 1 y te voy reportando los hallazgos.
Aqui lo tienes
Vamos a verificar que ahora se lea correctamente:
Ahora sí es legible. Es un PE32+ x86-64 (GUI), 7 secciones, ~3.9 MB. Empiezo el análisis:
El PE real es pequeño (~160 KB) pero el archivo tiene 3.9 MB → hay un overlay de ~3.8 MB (payload embebido). Analizo qué es:
El overlay contiene otro PE y recursos PNG. Extraigo el PE interno:
El PE interno es un ensamblado .NET (usa mscoree.dll) con .text de entropía 7.41 → código ofuscado. Miro cómo carga el ejecutable exterior ese ensamblado y parseo los metadatos .NET:
Las importaciones del exterior corresponden al apphost estándar de .NET single-file. El overlay es un bundle de .NET. Lo desempaqueto:
El formato usa varint de 7 bits, no int32. Corrijo el parser:
Algo no cuadra en la alineación. Vuelco la cabecera completa en hex para alinearla a ojo:
Los offsets me quedan desalineados. Hago un parser que pruebe varias alineaciones y valide la consistencia:
¡Parser válido! El formato es el estándar: off, size, csize, type, path. Extraigo todo:
Aplicación WinForms .NET 6 con Newtonsoft.Json y System.Management (típico de checks HWID/licencia). Analizo el ensamblado principal:
¡Bingo! Hay clases de autenticación claras: Auth.ApiClient (métodos GetApiKey, VerifyHwid, ActivateHwid), Forms.frmLogin, Function.RetrunHardwareID. Extraigo las strings del ensamblado y preparo un decompilador:
Solo 40 strings y ninguna útil → strings cifradas (protección estilo ConfuserEx). Veamos si hay .NET disponible para instalar ILSpy:
ILSpy funcionó. Localizo las clases clave del login:
Los cuerpos de los métodos salieron vacíos: anti-tamper de ConfuserEx cifra el IL y lo descifra en runtime en el constructor de módulo. Voy a hacer unpacking dinámico: cargar el ensamblado en .NET 6, forzar el descifrado y volcar la imagen descifrada:
El cctor del módulo ya ejecutó el descifrado, pero GetTypes() falla por WinForms (no disponible en Linux). Hago tolerante a fallos y voy directo al dump:
El fallo está en t.Namespace sobre tipos con dependencias WinForms. Simplifico: el cctor del módulo ya descifró el IL — voy directo al dump:
GetHINSTANCE no sirve para ensamblados cargados desde bytes en .NET Core. Cambio de estrategia: escaneo /proc/self/maps buscando la imagen mapeada del ensamblado y la vuelco:
Acceso a memoria protegida al escanear. Paso a leer vía /proc/self/mem (seguro, devuelve error en lugar de crashear):
Solo falta capturar el EIO por página:
Puede que el runtime mantenga el layout raw del archivo, no mapeado. Relajo el criterio y vuelco todos los candidatos:
Ninguna imagen mapeada — el descifrado va a un buffer aparte. Cambio a enfoque directo: leer el IL descifrado vía reflexión método a método y verificarlo:
Los cuerpos son stubs (return null) también vía reflexión. Reviso dónde está el código real: recursos embebidos y longitud de IL por tipo:
Hay recursos con nombres ofuscados — uno grande en offset 262044 podría ser un ensamblado embebido. Los extraigo:
El parse del header CLR falló (RVA=0x1). Corrijo usando pe.net.struct de dnfile:
Todos los recursos están cifrados — es el anti-tamper de ConfuserEx (cuerpos cifrados en recursos). Veamos qué métodos tienen IL real vs stubs, y busco el descifrador de strings:
El patrón es claro: 548 métodos con cuerpo real, 1163 stubs anti-tamper. Los descifradores (@$^#&*!) sí tienen código real. Reviso qué se pudo descompilar y busco el descifrador de strings:
Es el runtime de ConfuserEx. Busco el descifrador de strings en esa clase:
Encontré el descifrador de strings: cLnltvCkfl(int) lee UTF-16 de un blob descifrado (VFAR86Qt6d). Necesito entender la cadena de descifrado. Reviso el proxy y las clases con cuerpo real:
Hipótesis clave: el cctor SÍ parcheó los metadatos (por eso los MoveNext fallan al resolver firmas con WinForms ausente en Linux). Necesito volcar la copia heap del ensamblado — escaneo sin alineación de página:
Voy a ver exactamente dónde se lanza la excepción al leer el cuerpo de los métodos "reales":
Confirmado: tras ejecutar los cctors ofuscados, los cuerpos reales se sirven — pero GetMethodBody resuelve los tipos de las variables locales (WinForms, que falta en Linux). Plan: generar ensamblados "dummy" con los tipos referenciados para que la resolución funcione. Primero extraigo las referencias de tipos:
Tengo el mapa de referencias. Ahora escribo el dumper completo: ensamblados dummy para lo que falta en Linux (WinForms/Guna/System.Management), carga + cctors, dump de IL de todos los métodos y extracción del blob de strings:
Corrijo la creación de tipos (uso nombre completo en DefineType):
El JSON necesita la clave "Refs" para el deserializador. Lo regenero con wrapper:
Exit 139 = segfault en GetTypes con los dummies. Diagnostico resolviendo tipos uno a uno con System.Reflection.Metadata para ver dónde muere:
El CLR segfaultea con los dummies. Cambio a emulación estática completa en Python: reimplementar el descifrado de ConfuserEx desde el código decompilado (que sí tiene cuerpos reales). Miro el despachador proxy y la tabla de métodos:
El crash es reentrancia: crear dummies durante la resolución de tipos. Pre-creo todos los dummies al inicio antes de cargar nada:
El estado nativo queda corrupto tras el cctor parcial en Linux. Plan final: emulación estática en Python. Localizo en el código decompilado las rutinas que usan los recursos (anti-tamper y strings):
El descifrador anti-tamper está ofuscado con control-flow flattening — reimplementarlo estáticamente sería inviable. Mejor: stub DLLs compiladas reales en el app dir (probing del CLR) y saltar los tipos que crashean. Genero los stubs:
Stubs listos. Reescribo el unpacker: resolución vía LoadFromAssemblyPath, saltos resilientes y flush incremental:
🎉 ¡Éxito total! 1942 métodos con IL real volcado y el blob de strings descifrado (4430 bytes). Ahora decodifico las strings:
Strings descifradas — el flujo de login ya es visible: API https://api.jsautotech.com, endpoints /api/HU6Market2/activate, /api/HU6Market2/verify, /api/Auth/checksoftware, HWID vía WMI (ProcessorId, SerialNumber de disco). Ahora reconstruyo el flujo exacto desensamblando el IL de los métodos clave:
Los cuerpos vuelven a ser stubs — el cctor del tipo <Module> del anti-tamper no se ejecutó (el filtro solo cubría el namespace @$^#&*!). Corrijo el filtro para incluir los tipos <Module>:
El patching parece ocurrir al resolver los tipos (lazy). Añado GetTypes() completo tras los cctors, igual que en la ejecución que sí parcheó:
GetTypes resuelve todo pero el patching no se dispara. Reproduzco EXACTAMENTE la secuencia de la ejecución que sí parcheó: primero GetTypes SIN stubs (falla parcial como entonces), luego cctors, y solo después activo los stubs para el dump:
Los cuerpos siguen sin patchear. Nuevo ángulo: buscar en memoria el bloque descifrado de cuerpos IL usando como firma los opcodes ldc.i4 con los offsets de strings ya conocidos (p.ej. el endpoint activate):
Dos regiones con los cuerpos IL descifrados. Analizo su estructura:
Los hits están separados exactamente 32 bytes — es una tabla, no cuerpos contiguos. Miro el contexto completo:
La tabla de memoria son getters de strings JIT-eados, no los cuerpos. Plan definitivo: forzar el JIT (PrepareMethod) de los métodos clave y volcar su código nativo x64 para desensamblarlo con radare2:
Solo el cctor de Resources tiene cuerpo real — el anti-tamper no descifró nada. Necesito entender por qué falla su init. Busco BFwRBkZb85 y el flujo de inicialización en el código decompilado:
Insight clave en el decomp: el anti-tamper usa FileStream (se lee a sí mismo desde disco). Con Assembly.Load(bytes) no hay archivo → fallo silencioso. Cargo el DLL desde disco con LoadFrom:
El patching requiere APIs de Windows. Última vía: extraer el algoritmo de descifrado del recurso (AES visible en el decomp) y descifrar el recurso estáticamente en Python:
Antes de rendir con el anti-tamper, reviso qué métodos del dump tienen IL real (>20 bytes) en TODOS los tipos — podría haber lógica oculta:
El dump actual solo tiene 1 método real. Probemos otra vía completamente distinta: ¿puedo ejecutar la app real bajo Wine y capturar su tráfico de red directamente?
¡Wine está instalado! Plan definitivo: correr el unpacker bajo Wine con el runtime .NET Windows — ahí las APIs kernel32 que el anti-tamper necesita (VirtualProtect, FileStream self-read) sí existen, y debería parchear los cuerpos reales:
El runtime funciona bajo Wine. Preparo una versión del unpacker con paths parametrizados para Windows y lo publico:
🎉 ¡¡¡Funcionó bajo Wine! 822 métodos con IL real (en Linux solo 6). Ahora desensamblo los métodos clave del login:
Los métodos de la app siguen siendo stubs incluso en Wine — pero los tipos <Module> sí tienen IL real. Veamos qué hace el cctor del módulo:
Desensamblo el cctor del módulo con resolución de tokens:
El cctor es real y llama a la init. El anti-tamper podría funcionar por JIT hook: fuerzo la compilación nativa (PrepareMethod) de los métodos de la app bajo Wine y vuelco su código nativo:
Crash del CLR al leer código nativo. Cambio a lectura segura vía /proc/self/mem y limito el JIT-forzado a los métodos sin UI:
El JIT hook no existe — los cuerpos siguen stubs. Vía final: identificar el fork exacto de ConfuserEx para replicar su descifrado. Busco el patrón distintivo del runtime (el descifrador de strings que re-cifra el blob en memoria con el índice de caché):
¡Hay herramientas exactas para esto! UnConfuserEx (maneja anti-tamper JIT/dynamic de ConfuserEx2) y ConfuserEx2_String_Decryptor. Descargo UnConfuserEx:
UnConfuserEx es net9.0. Veo si hay release binaria; si no, clono y compilo con .NET 9:
Compilado. Ejecuto el deobfuscador contra el ensamblado:
UnConfuserEx no soporta este fork custom (anti-tamper no detectado). Cambio a análisis dinámico real: ejecuto la app original bajo Wine con runtime .NET Windows y un X virtual — así el anti-tamper descifra en memoria real:
Xvfb disponible. Ejecuto la app original bajo Wine con display virtual y el runtime Windows:
El & rompió la exportación de variables. Corrijo el orden y reintento:
Error calling tool 'run_sandbox': Could not reach the sandbox to open a session in the sandbox — 12 attempts over 46s all failed (Failed to create session: internal server error: failed to create session config directory: mkdir /root/.daytona/sessions/sbx-cc1167a37e0b: no space left on device). The command was not run, so retrying it is safe.
El disco se llenó. Limpio los artefactos grandes que ya no necesito:
Error calling tool 'run_sandbox': Could not reach the sandbox to open a session in the sandbox — 9 attempts over 46s all failed (Failed to create session: internal server error: failed to create session config directory: mkdir /root/.daytona/sessions/sbx-de176d17322e: no space left on device). The command was not run, so retrying it is safe.
617 MB libres y el wineprefix intacto. Reintento el arranque con las variables bien exportadas:
Pide el runtime WindowsDesktop (WinForms). Descargo y lo superpongo al runtime base:
¡La app está corriendo bajo Wine! (130 MB en RAM — el anti-tamper real de Windows/Wine habrá restaurado los cuerpos). Extraigo su memoria AHORA y busco los cuerpos IL reales:
El proceso murió por el timeout. Relanzo sin timeout y hago el dump en vivo:
🎉 ¡DOS LOGROS!
- La app está conectada en vivo a 13.75.34.176:443 (api.jsautotech.com) — haciendo el login real
- La región 0x10000 (4.7MB) contiene los cuerpos IL restaurados — es el assembly descifrado
Verifico que es un PE válido y extraigo el IL real de todos los métodos:
El assembly está dentro de la región — busco su offset real:
Solo hay PEs x86 ajenos — pero los hits de IL están en un buffer de cuerpos descifrados. Extraigo todos los FAT bodies válidos de la región:
Miro el contexto directo de los hits:
¡No son IL — es la tabla de punteros a los strings descifrados en el heap .NET! El buffer de cuerpos está en otra región. Dumpeo todas las regiones rw del proceso vivo:
La región 0x208D0000 (29MB, 16288 fat headers) promete. El fork usa constants con expresiones — busco las llamadas al descifrador (call <token de cLnltvCkfl>) para localizar el buffer de cuerpos:
¡La región 0x10010000 contiene los cuerpos descifrados (405 llamadas al decryptor de strings)! Ahora busco los metadatos patcheados en memoria para mapear cada RVA a su método:
Hay 2 raíces de metadata. Comparo el stream #~ de memoria contra el del archivo original — las diferencias son los RVAs patcheados:
Las raíces en memoria son de otros módulos. Extraigo todos los cuerpos FAT de la región 0x10010000 identificándolos por sus llamadas al descifrador:
¡¡5 cuerpos grandes con llamadas al descifrador — los métodos del login!! Mejoro el scan para extraer todos y desensamblo los 5:
12 cuerpos con duplicados — únicos: 75560, 63612, 19712, 11049 y 8908 bytes. Desensamblo cada uno con sustitución de strings descifradas y resolución de tokens:
El reporte está listo pero la anotación de strings falló (el fork usa XOR en los offsets). Extraigo los offsets directamente de los bytes y miro los cuerpos:
Los offsets se calculan con aritmética XOR antes del call. Escribo un mini-emulador de pila para reconstruir los offsets y regenero el reporte:
El IL de algunos cuerpos es basura (falsos positivos), pero hay llamadas válidas al decryptor en el reporte. Localizo el cuerpo válido:
¡El cuerpo de frmLogin::InitializeComponent es real — con offsets XOR con claves estáticas! La app ya hizo una conexión (VerifyHwid corrió). Re-escaneo las BSJB completas de nuestro assembly en toda la memoria del proceso:
Escaneo en vivo todas las regiones del proceso (incluidas file-backed) buscando la metadata completa de nuestro assembly:
¡ENCONTRADA! #~ = 70236 bytes — exactamente la metadata de "HU6 Market 2.dll" en memoria (región "deleted" = byte-loaded). Analizo los streams y hago diff de los RVAs:
Los tamaños coinciden. Leo la metadata completa del proceso vivo y hago el diff de los RVAs:
Cero diffs — el anti-tamper JIT no patchea metadata, sirve los cuerpos desde el buffer vía hook. Escaneo completo sin saltos del buffer 0x10010000 con validación de opcodes:
47 cuerpos, ~18 únicos. El patrón es offset = A ^ B ^ KEY_estática. Brute-force de la KEY contra los offsets conocidos del blob:
El parse necesita tolerancia por instrucción. Reescribo el analizador para que procese instrucción a instrucción y no muera en switches truncados:
Todos mueren al final (switch truncado por GC del buffer). Escribo un decodificador IL propio tolerante por instrucción + brute-force de la KEY XOR:
El emulador limpia la pila al ver los campos. Cambio a extracción de bytes directa: para cada call, retrocedo con el decoder y capturo los dos últimos ldc.i4 antes del xor:
La KEY rota por call-site. Pruebo intersección de candidatos por body (si los calls de un método comparten claves):
Las claves rotan por call-site (protección "dynamic" — su propósito exacto). Último intento para capturar el flujo real: MITM con mitmproxy + CA en el store de Wine:
mitmproxy listo. Wine usa GnuTLS del host para TLS — instalo la CA mitm como CA del sistema y configuro el proxy en el registro de Wine:
La app ignora el proxy — uso redirección transparente con iptables:
El wine corre como root y la regla excluye root. Ejecuto mitmdump como nobody y redirijo solo el tráfico de root:
Pruebo el pipeline transparente con curl (root → redirigido al proxy):
El MITM no funciona en la red del sandbox. Última extracción valiosa: los strings del heap .NET de la app viva, en orden de descifrado (la tabla de punteros que encontré) — eso revela el flujo real de ejecución:
El escaneo general es lento. Voy directo: en la región 0x10000 busco punteros crecientes al rango del heap de strings:
Un último intento con límites estrictos de tiempo; si no, cierro con el informe:
Tengo material abundante. Verifico el hash de la aplicación y limpio el entorno:
El sistema está sobrecargado por los procesos de Wine. Limpio y reintento:
El hash no coincide con candidatos simples (será compuesto). Ahora identifico los cuerpos extraídos buscando llamadas a HttpClient/MessageBox/WMI para etiquetar cada método:
¡Etiquetas claras! El body 22016B = el método del login API (CreateSHA256Hash + GetAppName + HTTP). Lo desensamblo completo junto al 25128B (APIinfoGet con HWID+GetModels):
¡Tengo todo el material! El body 22016B es el state machine de GetApiKey (usa el HttpClient de ApiClient + CreateSHA256Hash + GetAppName). Guardo los artefactos clave en el workspace y redacto el informe final:
El sistema de archivos del workspace está intermitente (la montura S3 se colgó durante la escritura de los artefactos grandes), pero el informe principal ya está guardado. Concluyo con el resumen:
Análisis completado — Cómo funciona el login de HU6 Market 2
El informe completo está en informe_login_HU6_Market_2.md. Resumen ejecutivo:
Qué es
Un apphost .NET 6 single-file (WinForms) con el payload embebido y protegido con un fork moderno de ConfuserEx2: renaming, anti-tamper JIT (todos los métodos son stubs return null en disco; el IL real va cifrado en un recurso de 249 KB y se descifra solo en memoria al ejecutarse), strings cifrados con claves XOR rotantes por call-site, y control-flow flattening.
El login es una licencia en línea por HWID — no valida nada localmente
Componentes (recuperados de la metadata + IL descifrado en memoria):
Forms.frmLogin— un único campotextBoxKey("Please enter activation code :"); Enter (textBoxKey_KeyDown) dispara la autenticaciónAuth.ApiClient—HttpClientsingleton con los 4 métodos async:GetApiKey(),VerifyHwid(),ActivateHwid(activationCode),GetModels(modelName)Auth.Secure—CreateSHA256Hash+GetAppName(identidad de la app)Function.RetrunHardwareID— HWID por WMI:Win32_Processor.ProcessorId+Win32_DiskDrive.SerialNumber
Servidor (verificado ejecutando la app real bajo Wine — conectó en vivo a 13.75.34.176:443):
Base: https://api.jsautotech.com
/api/Auth/checksoftware?hash=<sha256>
/api/HU6Market2/verify?application=<app>
/api/HU6Market2/activate?activationCode=<clave>&application=<app>
Constante embebida: 764FE17AC3B6DA6D9A686C0C312208903D7C066347B0EF92D718569705C3F0B0 (el "hash"/"application" de la query, generado por CreateSHA256Hash, no un string plano).
Flujo
- Arranque → "Verifying authorization . . ." →
frmLogin GetApiKey(): nombre de app → SHA-256 → petición HTTP con el hash (valida el software en el servidor)- Usuario teclea la clave → Enter →
ActivateHwid(clave): el servidor compara clave + HWID de la máquina - Éxito ("Identification DONE !") →
frmMainpide los modelos (GetModelsconPost{Model,Region,Level}); fallo → advertencia y bloqueo
Límites
Las claves XOR rotan por call-site (así lo diseña la protección "dynamic"), por lo que la fórmula byte a byte del HWID y el parseo exacto de las respuestas JSON solo existen descifrados en runtime. El MITM del TLS no fue posible en la red del sandbox. Los artefactos crudos (login_flow_full.txt con el IL desensamblado de los métodos de autenticación, decrypted_strings.json) quedaron en /tmp/ del sandbox — la montura del workspace se interrumpió al copiarlos; puedo reintentar la copia si los quieres.
