Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Mergen — Desofuscación mediante optimización con uso de LLVM IR y análisis de ensamblador. | Kitploit
Herramientas/GitHubGitHub/nac-l/mergen
Análisis EstáticoAnálisis Dinámico (Sandboxing)Ingeniería InversaAnálisis de BinariosExplotación de Binarios
GitHubnac-l/mergen

Mergen

Desofuscación mediante optimización con uso de LLVM IR y análisis de ensamblador.

Ver Repositorio
8789436hace 4 mesesRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Resumen del Proyecto:

Mergen es una herramienta diseñada para convertir código ensamblador en Representación Intermedia (IR) de LLVM. Esta herramienta está pensada para:

  • La desofuscación o devirtualización de código binario ofuscado
  • La mejora del proceso de ingeniería inversa, haciéndolo más eficiente y eficaz, especialmente en sistemas de software complejos.

Guía para compilar y ejecutar

Para compilar y ejecutar el proyecto, consulta docs/BUILDING.md.

Gate de regresión base

El trabajo de reescritura debe mantener el gate de regresión base en verde. El gate compila muestras PE específicas, ejecuta lifter y verifica las salidas de IR elevado.

  • Documentación del flujo de trabajo: docs/REWRITE_BASELINE.md
  • Gate de un comando: scripts\\rewrite\\run.cmd

Objetivos principales:

  • Desofuscación

  • Devirtualización

  • Optimización

  • ¿Cómo funciona?

    Ejecutamos simbólicamente (o elevamos simbólicamente) el objetivo. La idea aquí no es elevar instrucciones individuales, sino elevar una función completa. No esperamos que una instrucción ni un bloque básico se comporten igual cada vez; en cambio, los tratamos como si pudieran ser y de hecho son utilizados para diferentes propósitos en cada ocasión. Intentamos mantener el IR generado lo más simple y optimizable posible. También tenemos necesidades diferentes a las de un compilador habitual. Usamos análisis para evaluar el flujo de control. No podemos depender de LLVM para todo nuestro análisis, porque están creados para objetivos distintos y podrían no ser óptimos para nuestro caso de uso.

    image

    Ejemplos

    Este es el ejemplo práctico que ilustra cómo Mergen resuelve contra programas virtualizados.

    1. VMProtect
    2. Branches/Jumptables
    3. Themida 3.1.6.0 LION64 (Red)

    Example #1 (VMProtect)

    Este es nuestro programa objetivo

    root@kitploit:~
    struct test {
        int a;
        int b;
        int c;
    };
    
    int maths(test a, int b, int c) {
            return a.a  + b - c;
    }
    

    image

    image

    Configuración de VMProtect, todo está desactivado; virtualizamos la función en configuración ultra. (Versiones probadas 3.4.0-3.6.0 3.8.1)

    image

    image

    Aquí ejecutamos mergen. El primer argumento es el nombre del archivo y el segundo argumento es la dirección de la función. Mira lo sencillo que es ejecutarlo. Y podemos compilar la salida para explorarla con nuestro descompilador favorito.

    image

    root@kitploit:~
    ; ModuleID = 'my_lifting_module'
    source_filename = "my_lifting_module"
    
    ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read)
    define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %0, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readonly %memory) local_unnamed_addr #0 {
    entry:
      %stackmemory = alloca i128, i128 13758960, align 8
      %1 = trunc i64 %r8 to i32
      %2 = trunc i64 %rdx to i32
      %GEPLoadxd-5369456437- = getelementptr i8, ptr %memory, i64 %rcx
      %3 = load i32, ptr %GEPLoadxd-5369456437-, align 4
      %adc-temp-5370242400- = sub i32 %2, %1
      %realnot-5369532059- = add i32 %adc-temp-5370242400-, %3
      %stackmemory10243.sroa.55.1375304.insert.ext10255 = zext i32 %realnot-5369532059- to i64
      ret i64 %stackmemory10243.sroa.55.1375304.insert.ext10255
    }
    
    attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) }
    

    Después de compilar:

    image

    image

    Ahora te darás cuenta de que los registros están un poco descolocados. Esto se debe a que no seguimos las convenciones de llamada; si las siguiéramos, la firma de la función se vería así:

    root@kitploit:~
    define i64 @main(i64 %rcx, i64 %rdx, i64 %rdx, i64 %r8, i64 %r9 ...)
    

    Así que simplemente ajustamos la firma de la función para que se vea normal. Si tienes más preguntas sobre esta parte, te sugiero investigar las convenciones de llamada y el ABI.

    Example #2 (Branches/Jumptables)

    Entonces, digamos que tenemos este código. Las VM tomarán el siguiente código y lo convertirán en un salto indirecto, lo que es un poco más incómodo para el reverser.

    root@kitploit:~
    int maths(int a, int b, int c) {
        if (a > b)
            return a + b + c;
        else
            return a - b - c;
    }
    
    root@kitploit:~
    next_handler = xxx;
    if ( a-b > 0 )
      next_handler = yyy;
    jump next_handler;
    

    Siempre intentamos analizar los valores y hacerles seguimiento. Esto nos permite comprender el flujo de control. Para ramas tipo tabla de salto La salida optimizada sería una simple

    root@kitploit:~
    define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr nocapture readnone %TEB, ptr nocapture readnone %memory) local_unnamed_addr #0 {
    fake_ret:
      %0 = lshr i64 %rcx, 62
      %common.ret.op = and i64 %0, 2
      ret i64 %common.ret.op
    }
    

    Salida no optimizada. (DCE para legibilidad)

    root@kitploit:~
    source_filename = "my_lifting_module"
    
    define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr %TEB, ptr %memory) {
      %lsb = and i64 %rcx, 255
      %pf1 = mul i64 %lsb, 72340172838076673
      %pf2 = and i64 %pf1, -9205322385119247871
      %pf3 = urem i64 %pf2, 511
      %pf4 = and i64 %pf3, 1
      %pf5 = icmp eq i64 0, %pf4
      %0 = zext i1 %pf5 to i64
      %createrflag2 = shl i64 %0, 2
      %creatingrflag = or i64 2, %createrflag2
      %zeroflag = icmp eq i64 %rcx, 0
      %1 = zext i1 %zeroflag to i64
      %createrflag21 = shl i64 %1, 6
      %creatingrflag2 = or i64 %creatingrflag, %createrflag21
      %signflag = icmp slt i64 %rcx, 0
      %2 = zext i1 %signflag to i64
      %createrflag23 = shl i64 %2, 7
      %creatingrflag4 = or i64 %creatingrflag2, %createrflag23
      %GEPSTORE-5368713221- = getelementptr i8, ptr %memory, i64 1376032
      store i64 %creatingrflag4, ptr %GEPSTORE-5368713221-, align 4
      %realand-5368713229- = and i64 %creatingrflag4, 128
      %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7
      %3 = mul i64 %shr-lshr-5368713233-, 4
      %bvalue_indexvalue = add i64 5368713249, %3
      %4 = icmp eq i64 %bvalue_indexvalue, 5368713253
      %lolb- = select i1 %4, i64 5368713264, i64 5368713257
      %GEPSTORE-5368713248- = getelementptr i8, ptr %memory, i64 1376032
      store i64 %lolb-, ptr %GEPSTORE-5368713248-, align 4
      br i1 %4, label %real_ret, label %real_ret41
    
    real_ret:                                         ; preds = %fake_ret
      %inc-5368713273- = add i64 %shr-lshr-5368713233-, 1
      ret i64 %inc-5368713273-
    
    real_ret41:                                       ; preds = %fake_ret
      ret i64 %shr-lshr-5368713233-
    }
    

    Observa esta parte

    root@kitploit:~
      %realand-5368713229- = and i64 %creatingrflag4, 128
      %shr-lshr-5368713233- = lshr i64 %realand-5368713229-, 7
    

    Obtenemos las banderas, luego obtenemos el 7º bit, que es la bandera de signo (Sign Flag), y usamos la bandera de signo para calcular una dirección. Mediante el análisis, determinamos que la dirección podría ser uno de dos valores, 5368713257 o 5368713264, y luego lo convertimos en una comparación. Si la dirección es 5368713257, tomamos una rama; si es la otra, tomamos la otra. Al hacer esto, también es importante marcar la condición con el valor apropiado porque más adelante podríamos necesitar calcular otro salto con el mismo valor exacto.

    Aunque resolvemos los saltos indirectos, los saltos con más de 2 ubicaciones posibles no son compatibles. Esto se debe a que el análisis para ellos aún no está implementado. Esto nos permite resolver las ramas estilo VM, pero tenemos problemas con las tablas de salto de la vida real.

    Example #3 (Themida 3.1.6.0 LION64 (Red))

    Nuestro programa objetivo:

    image

    Configuración de Themida (solo nos importan las VM por ahora):

    image

    image

    image

    Después de la VM:

    image

    Ejecutando Mergen:

    image

    Código de salida: haz clic aquí Entonces, ¿por qué nuestro resultado no tiene tanto éxito como al elevar un binario protegido con VMP?

    Themida escribe activamente en la sección .themida. A diferencia de la pila, no podemos ignorar estas escrituras porque esos valores podrían ser leídos más tarde por otras cosas.

    Pero tenemos una solución temporal para eso. Eliminar todos los stores en la sección .themida. Como nuestro programa no escribe en memoria, solo comenté todos los stores. Ahora nos queda esto:

    root@kitploit:~
    source_filename = "my_lifting_module"
    
    define i64 @main(i64 %rax, i64 %rcx, i64 %rdx, i64 %rbx, i64 %rsp, i64 %rbp, i64 %rsi, i64 %rdi, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, ptr writeonly %memory) local_unnamed_addr #0 {
      %trunc = trunc i64 %r8 to i32
      %trunc1 = trunc i64 %rdx to i32
      %trunc2 = trunc i64 %rcx to i32
      %realadd-5369771371- = add i32 %trunc1, %trunc2
      %realadd-5369582686- = add i32 %realadd-5369771371-, %trunc
      %trunc457139 = zext i32 %realadd-5369582686- to i64
      ret i64 %trunc457139
    }
    
    attributes #0 = { mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: write) }
    

    Desafíos técnicos

    • Bucles
    • Código automodificable (especialmente con modificación condicional)
    • Encontrarse en un universo donde no existen los pases de "outlining" y "unrolling".

    Contacto

    Únete a nuestro Servidor de Discord de Mergen para intercambiar ideas o simplemente charlar.

    Descargar herramienta