
Un virtualizzatore di codice x86-64 per offuscamento basato su VM
Un virtualizzatore di codice x86-64 per offuscamento basato su VM.
*Il supporto PE è stato testato solo su binari compilati tramite MinGW-w64
CMake scaricherà tutte queste dipendenze, quindi non è necessario installarle manualmente.
| Nome | Versione |
|---|
| CMake | 3.25+ |
| Zydis | 4.1.0+ |
| zasm | Ultima |
| LIEF | 0.15.1+ |
È necessario un compilatore compatibile con C++23 per compilare.
git clone https://github.com/dmaivel/covirt.git
cd covirt
mkdir build
cd build
cmake ..
cmake --build . --config Release
Se stai compilando su Windows tramite Visual Studio, devi usare 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
Affinché covirt sappia quali funzioni devono essere virtualizzate, devi aggiungere i marcatori di inizio e fine nel tuo codice sorgente, in questo modo:
#include "covirt_stub.h"
int my_function(...)
{
int result = 0;
__covirt_vm_start();
// ...
__covirt_vm_end();
return result;
}
[!IMPORTANTE]
- Non posizionare
__covirt_vm_endin punti irraggiungibili (ad esempio dopo un return), poiché impedirà l'emissione dello stub di fine- Gli stub
__covirt_vm_...();non funzionano conMSVCperché usano assembly inline- È richiesto il supporto
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);
}
L'esempio sopra è stato virtualizzato usando covirt a.out -d, che produce un dump delle istruzioni VM dopo offuscamento e virtualizzazione. L'attuale implementazione VM spinge la maggior parte degli operandi nello stack per elaborarli, riducendo la complessità di codifica delle istruzioni VM. Per le istruzioni che non hanno un gestore VM definito, vengono eseguite nativamente (vm_exit -> istruzione nativa -> vm_enter). La chiamata a funzioni segue lo stesso flusso: usciamo, chiamiamo la funzione e rientriamo nella VM. Tutte queste trasformazioni fanno crescere notevolmente la dimensione dei binari:
a.out come ELF: 15.5 kB -> 1.0 MBa.out come PE: 259.3 kB -> 1.3 MB| Descrizione | IDA |
|---|---|
Decompilazione IDA di vm_entry, offuscata solo con il passaggio MBA. Il decompilatore ha generato oltre 27k righe di codice. | ![]() |
Disassemblaggio IDA di vm_entry, offuscata con i passaggi MBA e SMC. La decompilazione non funziona. | ![]() |