
Um virtualizador de código x86-64 para ofuscação baseada em VM.
Um virtualizador de código x86-64 para ofuscação baseada em VM.
*Suporte para PE testado apenas em binários compilados via MinGW-w64
O CMake buscará todas essas dependências, portanto, não é necessário instalá-las você mesmo.
| Nome | Versão |
|---|
| CMake | 3.25+ |
| Zydis | 4.1.0+ |
| zasm | Última |
| LIEF | 0.15.1+ |
É necessário um compilador compatível com C++23 para compilar.
git clone https://github.com/dmaivel/covirt.git
cd covirt
mkdir build
cd build
cmake ..
cmake --build . --config Release
Se você estiver compilando no Windows via Visual Studio, deve 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 o covirt saiba quais funções precisam ser virtualizadas, você deve adicionar os marcadores de início e fim ao seu código-fonte, como abaixo:
#include "covirt_stub.h"
int my_function(...)
{
int result = 0;
__covirt_vm_start();
// ...
__covirt_vm_end();
return result;
}
[!IMPORTANT]
- Não coloque
__covirt_vm_endem locais inalcançáveis (ou seja, após um return), pois isso impedirá a emissão do stub final- Os stubs
__covirt_vm_...();não funcionarão usandoMSVCporque usam assembly inline- É necessário suporte a
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);
}
O aplicativo de exemplo acima foi virtualizado usando covirt a.out -d, que gera um despejo das instruções da VM após a ofuscação e virtualização. A implementação atual da VM empurra a maioria dos operandos para a pilha para processá-los, reduzindo a complexidade da codificação das instruções da VM. Para instruções que não possuem um manipulador de VM definido, elas serão executadas nativamente (vm_exit -> native instruction -> vm_enter). Chamar funções segue o mesmo fluxo, no qual saímos, chamamos a função e reentramos na VM. No geral, as transformações fazem os binários crescerem significativamente em tamanho:
a.out como ELF: 15.5 kB -> 1.0 MBa.out como PE: 259.3 kB -> 1.3 MB| Descrição | IDA |
|---|---|
Descompilação do IDA de vm_entry, que foi ofuscado apenas pela passagem MBA. Mais de 27k LOC foram gerados pelo descompilador. | ![]() |
Desmontagem do IDA de vm_entry, que foi ofuscado pelas passagens MBA e SMC. A descompilação não funciona. | ![]() |