anti-reversing-techniques

Par wshobson · agents

Comprendre les techniques anti-reverse, d'obfuscation et de protection rencontrées lors de l'analyse de logiciels. Utilisez cette compétence lors de l'analyse de techniques d'évasion de malwares, lors de l'implémentation de protections anti-débogage pour des challenges CTF, lors du reverse engineering de binaires packés, ou lors de la création d'outils de recherche en sécurité devant détecter des environnements virtualisés.

npx skills add https://github.com/wshobson/agents --skill anti-reversing-techniques

USAGE AUTORISÉ UNIQUEMENT : Cette compétence contient des techniques de sécurité à double usage. Avant de procéder à tout contournement ou analyse :

  1. Vérifiez l'autorisation : Confirmez que vous avez une permission écrite explicite du propriétaire du logiciel, ou que vous opérez dans un contexte de sécurité légitime (CTF, pentest autorisé, analyse de malwares, recherche en sécurité)
  2. Documentez le périmètre : Assurez-vous que vos activités s'inscrivent dans le cadre défini de votre autorisation
  3. Conformité légale : Comprenez que le contournement non autorisé des protections logicielles peut violer les lois (CFAA, DMCA anti-contournement, etc.)

Cas d'usage légitimes : Analyse de malwares, pentest autorisé, compétitions CTF, recherche en sécurité académique, analyse de logiciels que vous possédez ou dont vous avez les droits

Techniques Anti-Rétro-Ingénierie

Comprendre les mécanismes de protection rencontrés lors d'analyses autorisées de logiciels, de recherche en sécurité et d'analyse de malwares. Ces connaissances aident les analystes à contourner les protections pour accomplir des tâches d'analyse légitimes.

Pour les techniques avancées, consultez references/advanced-techniques.md


Entrées / Sorties

Ce que vous fournissez :

  • Chemin du binaire ou échantillon : l'exécutable, DLL ou image firmware en cours d'analyse
  • Plateforme : Windows x86/x64, Linux, macOS, ARM — affecte les vérifications applicables
  • Objectif : contourner pour analyse dynamique, identifier le type de protection, construire du code de détection, implémenter pour CTF

Ce que cette compétence produit :

  • Identification de la protection : technique nommée (p. ex., vérification de timing RDTSC, PEB BeingDebugged) avec localisation dans le binaire
  • Stratégie de contournement : adresses de patch spécifiques, points d'accroche ou commandes d'outils pour neutraliser chaque vérification
  • Rapport d'analyse : conclusions structurées listant chaque couche de protection, sévérité et contournement recommandé
  • Artefacts de code : scripts Python/IDAPython, séquences de commandes GDB ou stubs C pour contourner ou implémenter des vérifications

Techniques Anti-Débogage

Anti-Débogage Windows

Détection Basée sur les API

// IsDebuggerPresent
if (IsDebuggerPresent()) {
    exit(1);
}

// CheckRemoteDebuggerPresent
BOOL debugged = FALSE;
CheckRemoteDebuggerPresent(GetCurrentProcess(), &debugged);
if (debugged) exit(1);

// NtQueryInformationProcess
typedef NTSTATUS (NTAPI *pNtQueryInformationProcess)(
    HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);

DWORD debugPort = 0;
NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessDebugPort,        // 7
    &debugPort,
    sizeof(debugPort),
    NULL
);
if (debugPort != 0) exit(1);

// Debug flags
DWORD debugFlags = 0;
NtQueryInformationProcess(
    GetCurrentProcess(),
    ProcessDebugFlags,       // 0x1F
    &debugFlags,
    sizeof(debugFlags),
    NULL
);
if (debugFlags == 0) exit(1);  // 0 means being debugged

Contournement : Utilisez le plugin ScyllaHide dans x64dbg (corrige automatiquement toutes les vérifications courantes). Manuellement : forcez le retour IsDebuggerPresent à 0, patchez PEB.BeingDebugged à 0, accrochez NtQueryInformationProcess. Dans IDA : ida_bytes.patch_byte(check_addr, 0x90).

Détection Basée sur le PEB

// Accès direct au PEB
#ifdef _WIN64
    PPEB peb = (PPEB)__readgsqword(0x60);
#else
    PPEB peb = (PPEB)__readfsdword(0x30);
#endif

// Drapeau BeingDebugged
if (peb->BeingDebugged) exit(1);

// NtGlobalFlag
// Débogué : 0x70 (FLG_HEAP_ENABLE_TAIL_CHECK |
//                 FLG_HEAP_ENABLE_FREE_CHECK |
//                 FLG_HEAP_VALIDATE_PARAMETERS)
if (peb->NtGlobalFlag & 0x70) exit(1);

// Drapeaux du heap
PDWORD heapFlags = (PDWORD)((PBYTE)peb->ProcessHeap + 0x70);
if (*heapFlags & 0x50000062) exit(1);

Contournement : Dans x64dbg, suivez gs:[60] (x64) ou fs:[30] (x86) dans le dump. Définissez BeingDebugged (offset +2) à 0 ; effacez NtGlobalFlag (offset +0xBC en x64).

Détection Basée sur le Timing

// Timing RDTSC
uint64_t start = __rdtsc();
// ... some code ...
uint64_t end = __rdtsc();
if ((end - start) > THRESHOLD) exit(1);

// QueryPerformanceCounter
LARGE_INTEGER start, end, freq;
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&start);
// ... code ...
QueryPerformanceCounter(&end);
double elapsed = (double)(end.QuadPart - start.QuadPart) / freq.QuadPart;
if (elapsed > 0.1) exit(1);  // Too slow = debugger

// GetTickCount
DWORD start = GetTickCount();
// ... code ...
if (GetTickCount() - start > 1000) exit(1);

Script Python — scanneur de détection anti-débogage basé sur le timing :

#!/usr/bin/env python3
"""Scannez un binaire pour les patterns anti-débogage basés sur le timing."""
import re
import sys

PATTERNS = {
    "RDTSC":              rb"\x0f\x31",                    # RDTSC opcode
    "RDTSCP":             rb"\x0f\x01\xf9",                # RDTSCP opcode
    "GetTickCount":       rb"GetTickCount\x00",
    "QueryPerfCounter":   rb"QueryPerformanceCounter\x00",
    "NtQuerySysInfo":     rb"NtQuerySystemInformation\x00",
}

def scan(path: str) -> None:
    data = open(path, "rb").read()
    print(f"Scanning: {path} ({len(data)} bytes)\n")
    for name, pattern in PATTERNS.items():
        hits = [m.start() for m in re.finditer(re.escape(pattern), data)]
        if hits:
            offsets = ", ".join(hex(h) for h in hits[:5])
            print(f"  [{name}] found at: {offsets}")
    print("\nDone. Cross-reference offsets in IDA/Ghidra to find check logic.")

if __name__ == "__main__":
    scan(sys.argv[1])

Contournement : Utilisez des points d'arrêt matériels (pas de surcharge INT3), NOP la comparaison + saut conditionnel, figez RDTSC via hyperviseur, ou accrochez les API de timing pour retourner des valeurs cohérentes.

Détection Basée sur les Exceptions

// SEH : si un débogueur est attaché, il consomme l'exception INT3
// et l'exécution tombe à exit(1) au lieu du gestionnaire __except
__try { __asm { int 3 } }
__except(EXCEPTION_EXECUTE_HANDLER) { return; }  // Clean: exception handled here
exit(1);  // Dirty: debugger swallowed the exception

// VEH : enregistrez un gestionnaire qui auto-gère INT3 (incrémente RIP après INT3)
// Le débogueur intercepte en premier, le gestionnaire ne s'exécute jamais → détecté
LONG CALLBACK VectoredHandler(PEXCEPTION_POINTERS ep) {
    if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_BREAKPOINT) {
        ep->ContextRecord->Rip++;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

Contournement : Dans x64dbg, définissez « Pass exception to program » pour EXCEPTION_BREAKPOINT (Options → Exceptions → add 0x80000003).

Anti-Débogage Linux

// ptrace auto-trace
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) {
    // Already being traced
    exit(1);
}

// /proc/self/status
FILE *f = fopen("/proc/self/status", "r");
char line[256];
while (fgets(line, sizeof(line), f)) {
    if (strncmp(line, "TracerPid:", 10) == 0) {
        int tracer_pid = atoi(line + 10);
        if (tracer_pid != 0) exit(1);
    }
}

// Vérification du processus parent
if (getppid() != 1 && strcmp(get_process_name(getppid()), "bash") != 0) {
    // Unusual parent (might be debugger)
}

Contournement (accroche LD_PRELOAD) :

# hook.c: long ptrace(int request, ...) { return 0; }
# gcc -shared -fPIC -o hook.so hook.c
LD_PRELOAD=./hook.so ./target

Séquence de commandes GDB pour contournement :

# 1. Faites que ptrace(PTRACE_TRACEME) retourne toujours 0 (succès)
catch syscall ptrace
commands
  silent
  set $rax = 0
  continue
end

# 2. Contournez la vérification après l'appel ptrace : trouvez "cmp rax, 0xffffffff; je <exit>"
#    Effacez ZF pour que le saut conditionnel ne soit pas pris :
#    set $eflags = $eflags & ~0x40

# 3. Contournez la vérification TracerPid de /proc/self/status au niveau open()
catch syscall openat
commands
  silent
  # Si arg contient "status", patchez le résultat fd vers /dev/null équivalent
  continue
end

# 4. Contournez la vérification du nom du processus parent
set follow-fork-mode child
set detach-on-fork off

Détection Anti-VM

Empreinte Matérielle

// Détection basée sur CPUID
int cpuid_info[4];
__cpuid(cpuid_info, 1);
// Vérifiez le bit hyperviseur (bit 31 d'ECX)
if (cpuid_info[2] & (1 << 31)) {
    // Running in hypervisor
}

// Chaîne de marque CPUID
__cpuid(cpuid_info, 0x40000000);
char vendor[13] = {0};
memcpy(vendor, &cpuid_info[1], 12);
// "VMwareVMware", "Microsoft Hv", "KVMKVMKVM", "VBoxVBoxVBox"

// Préfixe d'adresse MAC
// VMware: 00:0C:29, 00:50:56
// VirtualBox: 08:00:27
// Hyper-V: 00:15:5D

Détection par Registre/Fichier

// Clés de registre Windows
// HKLM\SOFTWARE\VMware, Inc.\VMware Tools
// HKLM\SOFTWARE\Oracle\VirtualBox Guest Additions
// HKLM\HARDWARE\ACPI\DSDT\VBOX__

// Fichiers
// C:\Windows\System32\drivers\vmmouse.sys
// C:\Windows\System32\drivers\vmhgfs.sys
// C:\Windows\System32\drivers\VBoxMouse.sys

// Processus
// vmtoolsd.exe, vmwaretray.exe
// VBoxService.exe, VBoxTray.exe

Détection Anti-VM Basée sur le Timing

// Les sorties VM causent des anomalies de timing
uint64_t start = __rdtsc();
__cpuid(cpuid_info, 0);  // Causes VM exit
uint64_t end = __rdtsc();
if ((end - start) > 500) {
    // Likely in VM (CPUID takes longer)
}

Contournement : Utilisez un environnement bare-metal, durcissez la VM (supprimez les outils invités, randomisez l'adresse MAC, supprimez les fichiers artefacts), patchez les branches de détection dans le binaire, ou utilisez FLARE-VM/REMnux avec des paramètres renforcés.

Pour la détection anti-VM avancée (calibrage delta RDTSC, port backdoor VMware, énumération de feuille hyperviseur, vérifications d'artefacts de pilotes invités), consultez references/advanced-techniques.md.


Obfuscation de Code

Obfuscation du Flux de Contrôle

Aplatissement du Flux de Contrôle

// Original
if (cond) {
    func_a();
} else {
    func_b();
}
func_c();

// Flattened
int state = 0;
while (1) {
    switch (state) {
        case 0:
            state = cond ? 1 : 2;
            break;
        case 1:
            func_a();
            state = 3;
            break;
        case 2:
            func_b();
            state = 3;
            break;
        case 3:
            func_c();
            return;
    }
}

Approche d'Analyse :

  • Identifiez la variable d'état
  • Mappez les transitions d'état
  • Reconstructisez le flux original
  • Outils : D-810 (IDA), SATURN

Prédicats Opaques

int x = rand();
if ((x * x) >= 0) { real_code(); }   // Always true  → junk_code() is dead
if ((x*(x+1)) % 2 == 1) { junk(); }  // Always false → consecutive product is even

Approche d'Analyse : Identifiez les expressions invariantes via exécution symbolique (angr, Triton), ou détectez les motifs opaques connus par patterns et supprimez-les.

Obfuscation des Données

Chiffrement de Chaînes

// Chiffrement XOR
char decrypt_string(char *enc, int len, char key) {
    char *dec = malloc(len + 1);
    for (int i = 0; i < len; i++) {
        dec[i] = enc[i] ^ key;
    }
    dec[len] = 0;
    return dec;
}

// Stack strings
char url[20];
url[0] = 'h'; url[1] = 't'; url[2] = 't'; url[3] = 'p';
url[4] = ':'; url[5] = '/'; url[6] = '/';
// ...

Approche d'Analyse :

# FLOSS pour désobfuscation automatique de chaînes
floss malware.exe

# Déchiffrement de chaînes IDAPython
def decrypt_xor(ea, length, key):
    result = ""
    for i in range(length):
        byte = ida_bytes.get_byte(ea + i)
        result += chr(byte ^ key)
    return result

Obfuscation des API

// Résolution d'API dynamique
typedef HANDLE (WINAPI *pCreateFileW)(LPCWSTR, DWORD, DWORD,
    LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE);

HMODULE kernel32 = LoadLibraryA("kernel32.dll");
pCreateFileW myCreateFile = (pCreateFileW)GetProcAddress(
    kernel32, "CreateFileW");

// Hachage d'API
DWORD hash_api(char *name) {
    DWORD hash = 0;
    while (*name) {
        hash = ((hash >> 13) | (hash << 19)) + *name++;
    }
    return hash;
}
// Résolvez par comparaison de hachage au lieu de chaîne

Approche d'Analyse : Identifiez l'algorithme de hachage, construisez une base de données de hachages de noms d'API connus, utilisez le plugin HashDB pour IDA, ou exécutez sous un débogueur pour laisser le binaire résoudre les appels à l'exécution.

Obfuscation au Niveau des Instructions

; Insertion de code mort — sémantiquement inerte mais pollue le désassemblage
push ebx / mov eax, 1 / pop ebx / xor ecx, ecx / add ecx, ecx

; Substitution d'instructions — même sémantique, encodage différent
xor eax, eax  →  sub eax, eax  |  mov eax, 0  |  and eax, 0
mov eax, 1    →  xor eax, eax; inc eax  |  push 1; pop eax

Pour les astuces anti-désassemblage avancées (instructions superposées, insertion de bytes parasites, code auto-modifiable, ROP comme obfuscation), consultez references/advanced-techniques.md.


Résumé des Stratégies de Contournement

Principes Généraux

  1. Comprenez la protection : Identifiez la technique utilisée
  2. Trouvez la vérification : Localisez le code de protection dans le binaire
  3. Patchez ou accrochez : Modifiez la vérification pour toujours passer
  4. Utilisez les outils appropriés : ScyllaHide, plugins x64dbg
  5. Documentez les conclusions : Conservez des notes sur les protections contournées

Recommandations d'Outils

Contournement anti-débogage :    ScyllaHide, TitanHide
Dépacking :           x64dbg + Scylla, OllyDumpEx
Désobfuscation :       D-810, SATURN, miasm
Analyse VM :         VMAttack, NoVmp, manual tracing
Déchiffrement de chaînes :   FLOSS, custom scripts
Exécution symbolique :  angr, Triton

Considérations Éthiques

Ces connaissances ne doivent être utilisées que pour :

  • La recherche en sécurité autorisée
  • L'analyse de malwares (défensive)
  • Les compétitions CTF
  • La compréhension des protections à des fins légitimes
  • Les objectifs éducatifs

N'utilisez jamais pour contourner les protections pour : le piratage de logiciels, l'accès non autorisé ou les objectifs malveillants.


Dépannage

La technique de détection fonctionne sur x86 mais pas sur ARM

RDTSC et CPUID sont spécifiques à x86. Sur ARM, utilisez MRS x0, PMCCNTR_EL0 (nécessite l'accès PMU du noyau) ou clock_gettime(CLOCK_MONOTONIC). PEB/TEB n'existent pas sur ARM — remplacez par /proc/self/status (Linux) ou task_info (macOS). Reconstruisez la logique de détection avec des API spécifiques à la plateforme.

Faux positif sur un débogueur ou outil d'analyse légitime

Les vérifications de timing se déclenchent lorsque Process Monitor ou les crochets AV gonflent la latence des appels système. Calibrez le seuil au démarrage : mesurez le chemin gardé 3 fois et utilisez moyenne + 3*écart-type. Pour les vérifications ptrace, vérifiez le nom de comm TracerPid via /proc/<pid>/comm avant de quitter — ce peut être un outil de monitoring non lié, pas un débogueur.

Le patch de contournement cause un crash au lieu de continuer l'exécution

Avant de NOP un saut conditionnel, tracez complètement la branche « détectée ». Si elle initialise ou libère un état de heap nécessaire plus tard, patcher le saut ignore cette configuration et corrompt l'état. À la place, patchez l'opérande de comparaison avec la valeur « propre » attendue, ou utilisez « Set condition to always false » du point d'arrêt dans x64dbg plutôt que de modifier les bytes.


Compétences Associées

  • binary-analysis-patterns — workflows d'analyse statique et dynamique pour ELF/PE/Mach-O
  • memory-forensics — acquisition de mémoire de processus, extraction d'artefacts et analyse en direct
  • protocol-reverse-engineering — décodage de protocoles binaires personnalisés et trafic réseau chiffré

Skills similaires