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
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.
| 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 |
Validation des headers DOS/PE, extraction des métadonnées critiques, vérification de l'architecture (x64).
VirtualAllocEx avec ImageBase préféré. Si échec, allocation alternative + gestion des relocations.
Mapping des sections .text, .rdata, .data avec leurs VirtualAddress et permissions correctes.
Parsing de l'Import Directory, chargement des DLL dépendantes, remplissage de l'IAT avec les adresses réelles.
Application des fixups si ImageBase différent de l'adresse préférée. Gestion des types HIGH/LOW, DIR64.
Appel des TLS Callbacks si présents, puis DllMain avec DLL_PROCESS_ATTACH. Gestion des exceptions.
; ====================================================================
; 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
; ====================================================================
; 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
; ====================================================================
; 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
; ====================================================================
; 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
Recherche de modules non listés dans PEB->Ldr mais présents en mémoire via VAD (Virtual Address Descriptor) analysis.
Hooker NtAllocateVirtualMemory, NtProtectVirtualMemory pour détecter les allocations RX anormales suivies de copies mémoire.
Détection des processus qui parsent manuellement des structures PE en mémoire via monitoring des accès mémoire patterns.
Vérification de l'intégrité des headers PE en mémoire (checksums, validité des structures).
Surveillance des modifications de l'IAT et détection des imports résolus dynamiquement hors LoadLibrary.
Détection des applications de relocations en mémoire via monitoring des écritures dans les sections .reloc.
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)
}
# 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
Objectif : Analyser le code ASM avec IDA Pro/Ghidra.
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
Objectif : Créer un outil de détection en C++.
Objectif : Modifier le code pour contourner les détections.
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.