Pourquoi détecter les VMs ?
Les machines virtuelles sont l'outil de prédilection pour le reverse engineering : elles permettent de prendre des snapshots, de revenir en arrière après un crash, d'inspecter la mémoire depuis l'hôte. Un attaquant qui analyse un programme sous IDA Pro ou Ghidra le fait presque toujours dans une VM.
La détection de VM sert aussi à protéger le système de licensing : une licence liée à une machine physique ne doit pas fonctionner dans un clone de VM. Un attaquant pourrait créer une VM identique à la machine cliente pour dupliquer la licence.
La détection de VM n'est pas infaillible — un attaquant patient peut masquer tous les artefacts. L'objectif est de rendre cela suffisamment coûteux pour décourager l'attaque automatisée.
Les 7 vecteurs de détection
IronLock v2 utilise 15 checks répartis sur 7 vecteurs indépendants, chacun avec un poids dans le score final.
1. CPUID et hypervisor flag
L'instruction CPUID avec EAX=1 retourne le bit 31 de ECX (Hypervisor Present Bit) à 1 si le processeur s'exécute dans une VM. De plus, CPUID avec EAX=0x40000000 retourne la chaîne identifiant le hypervisor :
import cpuinfo
def check_cpuid_hypervisor() -> tuple[bool, str]:
info = cpuinfo.get_cpu_info()
flags = info.get('flags', [])
brand = info.get('brand_raw', '').lower()
# Hypervisor flag dans CPUID
hypervisor_flag = 'hypervisor' in flags
# Strings hypervisor dans brand
VM_STRINGS = ['vmware', 'virtualbox', 'kvm',
'hyper-v', 'qemu', 'xen']
brand_match = any(s in brand for s in VM_STRINGS)
return hypervisor_flag or brand_match, brand
2. Registre Windows
Les outils de virtualisation installent des clés de registre caractéristiques. IronLock scanne 12 chemins connus :
VM_REGISTRY_KEYS = [
r'SOFTWARE\VMware, Inc.\VMware Tools',
r'SOFTWARE\Oracle\VirtualBox Guest Additions',
r'SYSTEM\CurrentControlSet\Servicesmbus', # Hyper-V
r'SYSTEM\CurrentControlSet\Services\VMSMP',
r'HARDWARE\ACPI\DSDT\VBOX__',
r'SOFTWARE\Microsoft\Virtual Machine\Guest', # Hyper-V
]
def check_registry() -> bool:
import winreg
for key in VM_REGISTRY_KEYS:
try:
winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, key)
return True # clé trouvée
except FileNotFoundError: pass
return False
3. Préfixes MAC d'hyperviseurs
Chaque hyperviseur attribue des adresses MAC avec des préfixes réservés :
VM_MAC_PREFIXES = {
'00:0c:29': 'VMware',
'00:50:56': 'VMware ESXi',
'08:00:27': 'VirtualBox',
'52:54:00': 'QEMU/KVM',
'00:15:5d': 'Hyper-V',
'00:1c:42': 'Parallels',
'00:16:3e': 'Xen',
}
4. Processus et drivers VM
Les Guest Additions et outils de VM lancent des processus reconnaissables : vmtoolsd, vmwaretray, vboxservice, vboxtray, xenservice, qemu-ga. Sur Linux, les modules kernel vboxguest, vmw_vmci, xenbus sont détectables via /proc/modules.
5. Timing side-channel
Les appels système dans une VM ont une variance plus élevée que sur métal nu. IronLock mesure 50 appels os.getpid() et calcule le coefficient de variation :
import time, os, statistics
def check_syscall_timing() -> bool:
timings = []
for _ in range(50):
t0 = time.perf_counter_ns()
os.getpid()
timings.append(time.perf_counter_ns() - t0)
mean = statistics.mean(timings)
stdev = statistics.stdev(timings)
cv = stdev / mean if mean > 0 else 0
return cv > 2.0 # VM probable si variance > 200%
Score et décision
Les 15 checks sont pondérés. Le seuil par défaut est 4/15 points pour déclencher l'action (configurable) :
VM_CHECKS = [
(check_cpuid_hypervisor, 3), # poids critique
(check_registry, 3),
(check_mac_prefix, 2),
(check_vm_processes, 2),
(check_vm_drivers, 3),
(check_syscall_timing, 1), # poids faible
(check_disk_model, 1),
]
Conclusion
La détection de VM combine 7 vecteurs indépendants pour réduire les faux positifs tout en couvrant tous les hyperviseurs majeurs. IronLock v2 intègre ces 15 checks nativement — leur comportement est configurable (bloquer, continuer ou alerter) selon le niveau de sécurité requis.