USO AUTORIZADO — ENTORNO LAB

Informe Anti-Reversing — LicenseGuard.exe

Análisis de protecciones, identificación de capas y estrategia de bypass — CULTIVA IA Security Team
Muestra LicenseGuard.exe (PE64, MSVC 2022 + OLLVM 4.0)
SHA-256 a3f8c2d1...f2a3b4
Plataforma Windows x64
Fecha análisis 2026-06-16
Analista CULTIVA IA Security
Skill tecnicas-anti-reversing v1.0
📋
Resumen Ejecutivo
Visión general del análisis de protecciones en LicenseGuard.exe
🛡
Uso autorizado únicamente. Este informe documenta protecciones de software interno de CULTIVA IA en un entorno de laboratorio aislado. La aplicación de estas técnicas fuera del ámbito autorizado puede violar la DMCA y normativas equivalentes (LSSICE en España).
2
Críticas
2
Altas
2
Medias
6
Total capas

LicenseGuard.exe implementa un stack de protección multicapa de alta sofisticación. El binario combina técnicas de anti-debugging basadas en API de Windows (PEB, NtQueryInformationProcess) con detección por temporización (RDTSC), código auto-modificante en la sección .prot, ofuscación de flujo de control via OLLVM, ofuscación de strings XOR y detección de entorno virtual. El índice de dificultad de reversing se estima en 78/100. Se han identificado vectores de bypass para todas las capas.

🔍
Hallazgos por Capa de Protección
6 técnicas identificadas — ordenadas por severidad de dificultad de bypass
Crítica Anti-Debug API + PEB.BeingDebugged 0x14001A230 – 0x14001A2F0
Tres capas de detección apiladas: IsDebuggerPresent (IAT hook-resistant via inline PEB read), NtQueryInformationProcess(ProcessDebugPort) directo a ntdll sin wrapper, y lectura del flag NtGlobalFlag (0x70). Si alguna falla, activa rutina de corrupción de heap silenciosa en lugar de terminar el proceso.
Técnica PEB offset gs:[60h]+2 (BeingDebugged), gs:[60h]+BC (NtGlobalFlag), syscall directo ProcessDebugPort
Efecto Corrupción silenciosa de heap → crash diferido en validación de licencia ~30s después
Herramienta ScyllaHide (x64dbg plugin) — cubre automáticamente las 3 variantes
; ASM encontrado en 0x14001A230 (x64)
mov    rax, qword ptr gs:[60h]        ; PEB pointer
movzx  eax, byte ptr [rax+2]           ; PEB.BeingDebugged
test   al, al
jnz    corrupt_heap_14001A2F0

mov    rax, qword ptr gs:[60h]
mov    eax, dword ptr [rax+0BCh]      ; NtGlobalFlag
and    eax, 70h
jnz    corrupt_heap_14001A2F0
Crítica Timing Check RDTSC + QueryPerformanceCounter 0x14002B100 – 0x14002B1E0
Doble timing check: RDTSC en el prólogo de la función de validación de licencia (umbral: <5000 ciclos), y QueryPerformanceCounter como respaldo (umbral: <500μs). El umbral RDTSC es muy agresivo y dispara falsos positivos con hooks AV. El código de detección está inlined, no es una llamada externa.
Técnica RDTSC delta + QPC delta con umbral dual
Efecto Retorno de clave de licencia inválida (sin crash observable) — fallo silencioso
Bypass Parchear JNZ → JMP en 0x14002B158; calibrar umbral a media+3*stddev en startup
; RDTSC timing check — 0x14002B100
rdtsc
shl    rdx, 32
or     rax, rdx
mov    [rbp-8], rax               ; t_start
; ... cuerpo función ...
rdtsc
shl    rdx, 32
or     rax, rdx
sub    rax, [rbp-8]               ; delta
cmp    rax, 5000
jg     return_invalid_key         ; ← parchear a JMP past
Alta Self-Modifying Code — sección .prot Sección .prot @ 0x140050000
La sección .prot (tamaño: 48KB, flags: RWX) contiene código cifrado que se descifra en runtime con clave derivada del hash del proceso. Si se detecta un breakpoint de software (0xCC) en memoria, el descifrado usa una clave alternativa que produce código no funcional.
Cifrado XOR con clave de 32 bytes derivada de CRC32(ModuleName + ProcessId)
Detección BP Escaneo de 0xCC en rangos de función antes del descifrado
Bypass Usar hardware breakpoints (DR0–DR3) en lugar de software BPs; dumping de .prot post-descifrado
; Scan de software breakpoints antes de descifrar
lea    rcx, [func_start]
mov    rdx, func_size
scan_loop:
  cmp  byte ptr [rcx], 0CCh        ; INT3?
  jz   use_wrong_key
  inc  rcx
  dec  rdx
  jnz  scan_loop
call decrypt_with_real_key
Alta OLLVM Control Flow Flattening Múltiples funciones — 23 afectadas
Obfuscator-LLVM aplica Control Flow Flattening a 23 funciones críticas (incluyendo la lógica de validación). Todas las funciones tienen un dispatcher switch centralizado con variable de estado, haciendo el grafo de flujo prácticamente ilegible en IDA/Ghidra. Adicionalmente, bogus control flow inserta ramas muertas con predicados opacos.
Técnica CFF (switch dispatcher) + Bogus Control Flow + Instruction Substitution
Impacto CFG 10-15x más grande que el original; análisis manual impracticable
Herramienta D-810 (IDA plugin) o LLVM-Deobfuscator para deflattening automático
Media String Obfuscation — XOR Rotativo Distribuido — ~340 strings
Todas las strings de error, URLs y mensajes de licencia están cifradas con XOR rotativo de clave de 4 bytes. La clave cambia según el índice del string (key[i % 4] XOR i). Las strings se descifran inline en el stack frame antes de uso.
Clave 0x4C, 0x49, 0x43, 0x4B ("LICK") rotativa con XOR del índice
Bypass Script IDAPython (ver Sección 4) — descifra y renombra todas las strings automáticamente
Media Anti-VM / Anti-Sandbox Detection 0x14003C400 – 0x14003C580
Detecta VMware (VMWARE~1 en disco, backdoor port 0x5658), VirtualBox (VBoxGuest driver, MAC 08:00:27:xx), y sandboxes de análisis (username "sandbox", "malware", "virus"; hostname "ANALYSIS"). También verifica número de procesos <20 y tiempo de uptime <10min.
Bypass Usar físico o VM con artifacts limpiados; parchear JNZ en 0x14003C4F8 y 0x14003C530
Estrategia de Bypass — x64dbg
Secuencia de pasos para análisis dinámico completo
Herramientas requeridas: x64dbg + ScyllaHide plugin + D-810 IDA plugin (deflattening OLLVM) + Python 3.11 con idat64.exe en PATH.
Paso Capa Acción Estado
01 Entorno Limpiar artifacts VM en la máquina de análisis. Renombrar username → "user001", hostname → "WS-CORP-42", poner >30 procesos corriendo. Prep
02 ScyllaHide Cargar LicenseGuard.exe en x64dbg. Activar ScyllaHide con perfil "Advanced" — cubre PEB.BeingDebugged, NtGlobalFlag, NtQueryInformationProcess automáticamente. Auto
03 RDTSC En x64dbg → Options → Preferences → "Replace RDTSC with fake values". Alternativa: parchear JG en 0x14002B158 → JMP SHORT (EB 05). NO usar NOP — el "detected" branch inicializa estado necesario. Manual
04 Self-Modifying Usar EXCLUSIVAMENTE hardware breakpoints (DR0–DR3) en .prot. Poner HW BP en exec en OEP de cada función descifrada. Nunca INT3 en rango 0x140050000–0x14005BFFF. Manual
05 Anti-VM Parchear JNZ en 0x14003C4F8 → JMP SHORT (EB 05) y en 0x14003C530 → NOP NOP (no hay setup en el branch). Verificar antes de NOPear. Manual
06 Strings Ejecutar script IDAPython de deofuscación (Sección 4) en IDA Pro. Volver a cargar database. Todas las ~340 strings son ahora legibles en IDA. Script
07 OLLVM CFF Instalar D-810 en IDA. Run → D-810 → "Deobfuscate all functions". Tarda ~4min en el binario de 1.24MB. El CFG queda limpio para análisis de lógica de licencia. Plugin
🐍
Scripts IDAPython
Automatización de bypass y análisis para IDA Pro 8.x
IDAPython patch_peb_checks.py — Parchea todas las comprobaciones PEB/NtGlobalFlag del binario
import idaapi, ida_bytes, idc

# Parchea BeingDebugged checks (mov rax, gs:[60]; movzx eax, [rax+2]; test al,al; jnz)
PEB_CHECKS = [
    0x14001A230,   # check #1
    0x14001A290,   # check #2 (redundante)
    0x14002A100,   # check en módulo .prot post-decrypt
]

NTGLOBAL_CHECKS = [
    0x14001A250,   # NtGlobalFlag & 0x70
    0x14002A120,
]

def patch_jnz_to_jmp_past(addr, offset=2):
    """Convierte JNZ (75 XX) en JMP SHORT ignorando el branch"""
    orig = ida_bytes.get_byte(addr)
    if orig == 0x75:  # JNZ
        ida_bytes.patch_byte(addr, 0xEB)   # JMP SHORT
        print(f"Patched JNZ→JMP at 0x{addr:X}")
    else:
        print(f"[WARN] Expected JNZ at 0x{addr:X}, got 0x{orig:02X}")

for addr in PEB_CHECKS + NTGLOBAL_CHECKS:
    # La instrucción JNZ está 5 bytes después del test al,al
    patch_jnz_to_jmp_past(addr + 5)

print("[+] PEB / NtGlobalFlag checks patched.")
IDAPython deobfuscate_strings.py — Descifra las ~340 strings XOR y las renombra en IDA
import idc, idautils, ida_bytes, ida_name

XOR_KEY = [0x4C, 0x49, 0x43, 0x4B]   # "LICK"
DATA_SEG = 0x140040000
DATA_END = 0x14004FFFF

def decrypt_xor_string(ea):
    result = []
    i = 0
    while True:
        b = ida_bytes.get_byte(ea + i)
        dec = b ^ (XOR_KEY[i % 4] ^ i)
        if dec == 0: break
        result.append(chr(dec))
        i += 1
        if i > 512: break   # sanity
    return "".join(result)

count = 0
for ea in idautils.Strings():
    if DATA_SEG <= int(ea) <= DATA_END:
        dec = decrypt_xor_string(int(ea))
        if len(dec) > 3:
            ida_name.set_name(int(ea), f"str_{dec[:24].replace(' ','_')}")
            idc.set_cmt(int(ea), f'[DECRYPTED] "{dec}"', 0)
            count += 1

print(f"[+] Decrypted and renamed {count} strings.")
IDAPython patch_rdtsc.py — Neutraliza los timing checks RDTSC con calibración dinámica
import idc, ida_bytes, idc, idautils

# Direcciones de los JG/JA post-RDTSC encontradas en el análisis estático
TIMING_JUMPS = [
    (0x14002B158, "rdtsc_check_main"),
    (0x14002B1A0, "qpc_check_main"),
    (0x14003A280, "rdtsc_check_prot"),
]

for addr, name in TIMING_JUMPS:
    opcode = ida_bytes.get_byte(addr)
    if opcode in (0x7F, 0x77):   # JG / JA short
        # Convertir en JMP SHORT al siguiente bloque (omite detected branch)
        dest_off = idc.get_operand_value(addr, 0)
        ida_bytes.patch_byte(addr, 0xEB)
        print(f"[+] Patched timing JG→JMP at 0x{addr:X} ({name})")
    else:
        print(f"[WARN] Unexpected opcode 0x{opcode:02X} at 0x{addr:X}")

print("[+] All RDTSC/QPC timing checks neutralized.")
Recomendaciones Post-Análisis
Pasos siguientes y skills relacionadas
Antes de modificar el binario en producción: toda la lógica de validación identificada debe documentarse y discutirse con el equipo de desarrollo. Los patches de análisis NO deben llegar a producción — solo sirven para el lab.
Inmediato (Esta semana)
  • Ejecutar los 3 scripts IDAPython y verificar resultados
  • Dump de sección .prot post-descifrado con Scylla
  • Documentar la lógica de validación de clave encontrada
  • Reportar el umbral RDTSC agresivo al equipo dev (causa falsos positivos con AV hooks)
Próximo sprint
  • Calibrar umbral RDTSC: medir en prod (mean + 3*stddev)
  • Reemplazar OLLVM CFF por técnica más mantenible en builds internas
  • Añadir memoria de análisis a skill binary-analysis-patterns
  • Crear skill derivada: license-validation-bypass-patterns
Skills relacionadas en CULTIVA Arsenal
binary-analysis-patterns memory-forensics protocol-reverse-engineering security-review