
Un virtualizador de código x86-64 para ofuscación basada en VM
Un virtualizador de código x86-64 para ofuscación basada en VM.
*El soporte para PE solo se ha probado en binarios compilados con MinGW-w64
CMake descargará todas estas dependencias, por lo que no es necesario instalarlas tú mismo.
| Nombre | Versión |
|---|
| CMake | 3.25+ |
| Zydis | 4.1.0+ |
| zasm | Última |
| LIEF | 0.15.1+ |
Se requiere un compilador compatible con C++23 para compilar.
git clone https://github.com/dmaivel/covirt.git
cd covirt
mkdir build
cd build
cmake ..
cmake --build . --config Release
Si compilas en Windows con Visual Studio, debes usar clang-cl: cmake .. -T ClangCL -A x64.
Usage: covirt [--help] [--version] [--output OUTPUT_PATH] [--vm_code_size MAX] [--vm_stack_size SIZE] [--no_self_modifying_code] [--no_mixed_boolean_arith] [--show_dump_table] INPUT_PATH
Code virtualizer for x86-64 ELF & PE binaries
Positional arguments:
INPUT_PATH path to input binary to virtualize
Optional arguments:
-h, --help shows help message and exits
-v, --version prints version information and exits
-o, --output OUTPUT_PATH specify the output file [default: INPUT_PATH.covirt]
-vcode, --vm_code_size MAX specify the maximum allowed total lifted bytes [default: 2048]
-vstack, --vm_stack_size SIZE specify the size of the virtual stack [default: 2048]
-no_smc, --no_self_modifying_code disable smc pass
-no_mba, --no_mixed_boolean_arith disable mba pass
-d, --show_dump_table show disassembly of the vm instructions
Para que covirt sepa qué funciones deben virtualizarse, debes añadir los marcadores de inicio y fin en tu código fuente, de la siguiente manera:
#include "covirt_stub.h"
int my_function(...)
{
int result = 0;
__covirt_vm_start();
// ...
__covirt_vm_end();
return result;
}
[!IMPORTANT]
- No coloques
__covirt_vm_enden ubicaciones inalcanzables (p. ej., después de unreturn), ya que impedirá que se emita el stub final- Los stubs
__covirt_vm_...();no funcionan conMSVCporque usan ensamblado en línea- Se requiere soporte de
SSE4

#include <covirt_stub.h>
#include <stdio.h>
int calculate(int a, int b)
{
int result = 0;
__covirt_vm_start();
for (int i = 0; i < 10; i++)
if (i > 5)
result += result + a;
else
result += (result >> 1) + b;
printf("result = %d\n", result);
__covirt_vm_end();
return result;
}
int main()
{
calculate(5, 12);
}
La aplicación de ejemplo anterior se virtualizó usando covirt a.out -d, que genera un volcado de las instrucciones de la VM después de la ofuscación y la virtualización. La implementación actual de la VM inserta la mayoría de los operandos en la pila para procesarlos, lo que reduce la complejidad de codificar las instrucciones de la VM. Las instrucciones que no tienen un handler de VM definido se ejecutan de forma nativa (vm_exit -> native instruction -> vm_enter). La llamada a funciones sigue el mismo flujo: salimos, llamamos a la función y volvemos a entrar en la VM. En conjunto, las transformaciones hacen que los binarios crezcan significativamente en tamaño:
a.out como ELF: 15.5 kB -> 1.0 MBa.out como PE: 259.3 kB -> 1.3 MB| Descripción | IDA |
|---|---|
Descompilación de vm_entry en IDA, ofuscado solo con el pase de MBA. El descompilador generó más de 27k LOC. | ![]() |
Desensamblado de vm_entry en IDA, ofuscado con los pases de MBA y SMC. La descompilación no funciona. | ![]() |