binary-analysis-patterns

Par wshobson · agents

Maîtrisez les techniques d'analyse binaire : désassemblage, décompilation, analyse du flux de contrôle et reconnaissance de patterns dans le code. À utiliser pour analyser des exécutables, comprendre du code compilé ou effectuer une analyse statique sur des binaires.

npx skills add https://github.com/wshobson/agents --skill binary-analysis-patterns

Motifs d'analyse binaire

Motifs et techniques complets pour analyser les binaires compilés, comprendre le code assembleur et reconstituer la logique du programme.

Fondamentaux du désassemblage

Motifs d'instructions x86-64

Prologue/Épilogue de fonction

; Prologue standard
push rbp           ; Sauvegarde le pointeur de base
mov rbp, rsp       ; Configure le cadre de pile
sub rsp, 0x20      ; Alloue les variables locales

; Fonction feuille (aucun appel)
; Peut ignorer la configuration du pointeur de frame
sub rsp, 0x18      ; Alloue simplement les locales

; Épilogue standard
mov rsp, rbp       ; Restaure le pointeur de pile
pop rbp            ; Restaure le pointeur de base
ret

; Instruction leave (équivalente)
leave              ; mov rsp, rbp; pop rbp
ret

Conventions d'appel

System V AMD64 (Linux, macOS)

; Arguments: RDI, RSI, RDX, RCX, R8, R9, puis pile
; Retour: RAX (et RDX pour 128 bits)
; Sauvegardés par l'appelant: RAX, RCX, RDX, RSI, RDI, R8-R11
; Sauvegardés par l'appelé: RBX, RBP, R12-R15

; Exemple: func(a, b, c, d, e, f, g)
mov rdi, [a]       ; 1er argument
mov rsi, [b]       ; 2e argument
mov rdx, [c]       ; 3e argument
mov rcx, [d]       ; 4e argument
mov r8, [e]        ; 5e argument
mov r9, [f]        ; 6e argument
push [g]           ; 7e argument sur la pile
call func

Microsoft x64 (Windows)

; Arguments: RCX, RDX, R8, R9, puis pile
; Espace d'ombre: 32 octets réservés sur la pile
; Retour: RAX

; Exemple: func(a, b, c, d, e)
sub rsp, 0x28      ; Espace d'ombre + alignement
mov rcx, [a]       ; 1er argument
mov rdx, [b]       ; 2e argument
mov r8, [c]        ; 3e argument
mov r9, [d]        ; 4e argument
mov [rsp+0x20], [e] ; 5e argument sur la pile
call func
add rsp, 0x28

Motifs d'assembleur ARM

Convention d'appel ARM64 (AArch64)

; Arguments: X0-X7
; Retour: X0 (et X1 pour 128 bits)
; Pointeur de frame: X29
; Registre de lien: X30

; Prologue de fonction
stp x29, x30, [sp, #-16]!  ; Sauvegarde FP et LR
mov x29, sp                 ; Configure le pointeur de frame

; Épilogue de fonction
ldp x29, x30, [sp], #16    ; Restaure FP et LR
ret

Convention d'appel ARM32

; Arguments: R0-R3, puis pile
; Retour: R0 (et R1 pour 64 bits)
; Registre de lien: LR (R14)

; Prologue de fonction
push {fp, lr}
add fp, sp, #4

; Épilogue de fonction
pop {fp, pc}    ; Retour en dépilant PC

Motifs de flux de contrôle

Branches conditionnelles

; if (a == b)
cmp eax, ebx
jne skip_block
; ... corps du if ...
skip_block:

; if (a < b) - signé
cmp eax, ebx
jge skip_block    ; Saute si supérieur ou égal
; ... corps du if ...
skip_block:

; if (a < b) - non signé
cmp eax, ebx
jae skip_block    ; Saute si supérieur ou égal (non signé)
; ... corps du if ...
skip_block:

Motifs de boucles

; for (int i = 0; i < n; i++)
xor ecx, ecx           ; i = 0
loop_start:
cmp ecx, [n]           ; i < n
jge loop_end
; ... corps de la boucle ...
inc ecx                ; i++
jmp loop_start
loop_end:

; while (condition)
jmp loop_check
loop_body:
; ... corps ...
loop_check:
cmp eax, ebx
jl loop_body

; do-while
loop_body:
; ... corps ...
cmp eax, ebx
jl loop_body

Motifs de déclaration switch

; Motif de table de sauts
mov eax, [switch_var]
cmp eax, max_case
ja default_case
jmp [jump_table + eax*8]

; Comparaison séquentielle (petit switch)
cmp eax, 1
je case_1
cmp eax, 2
je case_2
cmp eax, 3
je case_3
jmp default_case

Motifs de structures de données

Accès aux tableaux

; array[i] - éléments de 4 octets
mov eax, [rbx + rcx*4]        ; rbx=base, rcx=index

; array[i] - éléments de 8 octets
mov rax, [rbx + rcx*8]

; Tableau multidimensionnel array[i][j]
; arr[i][j] = base + (i * cols + j) * element_size
imul eax, [cols]
add eax, [j]
mov edx, [rbx + rax*4]

Accès à la structure

struct Example {
    int a;      // décalage 0
    char b;     // décalage 4
    // rembourrage  // décalage 5-7
    long c;     // décalage 8
    short d;    // décalage 16
};
; Accès aux champs de la structure
mov rdi, [struct_ptr]
mov eax, [rdi]         ; s->a (décalage 0)
movzx eax, byte [rdi+4] ; s->b (décalage 4)
mov rax, [rdi+8]       ; s->c (décalage 8)
movzx eax, word [rdi+16] ; s->d (décalage 16)

Traversée de liste chaînée

; while (node != NULL)
list_loop:
test rdi, rdi          ; node == NULL?
jz list_done
; ... traite le nœud ...
mov rdi, [rdi+8]       ; node = node->next (en supposant next au décalage 8)
jmp list_loop
list_done:

Motifs de code courants

Opérations sur les chaînes

; Motif strlen
xor ecx, ecx
strlen_loop:
cmp byte [rdi + rcx], 0
je strlen_done
inc ecx
jmp strlen_loop
strlen_done:
; ecx contient la longueur

; Motif strcpy
strcpy_loop:
mov al, [rsi]
mov [rdi], al
test al, al
jz strcpy_done
inc rsi
inc rdi
jmp strcpy_loop
strcpy_done:

; memcpy avec rep movsb
mov rdi, dest
mov rsi, src
mov rcx, count
rep movsb

Motifs arithmétiques

; Multiplication par constante
; x * 3
lea eax, [rax + rax*2]

; x * 5
lea eax, [rax + rax*4]

; x * 10
lea eax, [rax + rax*4]  ; x * 5
add eax, eax            ; * 2

; Division par puissance de 2 (signé)
mov eax, [x]
cdq                     ; Étend le signe à EDX:EAX
and edx, 7              ; Pour diviser par 8
add eax, edx            ; Ajuste pour négatif
sar eax, 3              ; Décalage arithmétique vers la droite

; Modulo puissance de 2
and eax, 7              ; x % 8

Manipulation de bits

; Teste un bit spécifique
test eax, 0x80          ; Teste le bit 7
jnz bit_set

; Définit le bit
or eax, 0x10            ; Définit le bit 4

; Efface le bit
and eax, ~0x10          ; Efface le bit 4

; Bascule le bit
xor eax, 0x10           ; Bascule le bit 4

; Compte les zéros de tête
bsr eax, ecx            ; Bit scan reverse
xor eax, 31             ; Convertit en zéros de tête

; Population count (popcnt)
popcnt eax, ecx         ; Compte les bits définis

Motifs de décompilation

Récupération de variables

; Variable locale à rbp-8
mov qword [rbp-8], rax  ; Stocke en local
mov rax, [rbp-8]        ; Charge depuis local

; Tableau alloué sur la pile
lea rax, [rbp-0x40]     ; Le tableau commence à rbp-0x40
mov [rax], edx          ; array[0] = valeur
mov [rax+4], ecx        ; array[1] = valeur

Récupération de signature de fonction

; Identifie les paramètres par utilisation de registre
func:
    ; rdi utilisé comme premier paramètre (System V)
    mov [rbp-8], rdi    ; Sauvegarde le paramètre en local
    ; rsi utilisé comme deuxième paramètre
    mov [rbp-16], rsi
    ; Identifie le retour par RAX à la fin
    mov rax, [result]
    ret

Récupération de type

; Les opérations sur 1 octet suggèrent char/bool
movzx eax, byte [rdi]   ; Étend zéro l'octet
movsx eax, byte [rdi]   ; Étend le signe de l'octet

; Les opérations sur 2 octets suggèrent short
movzx eax, word [rdi]
movsx eax, word [rdi]

; Les opérations sur 4 octets suggèrent int/float
mov eax, [rdi]
movss xmm0, [rdi]       ; Float

; Les opérations sur 8 octets suggèrent long/double/pointeur
mov rax, [rdi]
movsd xmm0, [rdi]       ; Double

Conseils d'analyse Ghidra

Amélioration de la décompilation

// Dans le scripting Ghidra
// Corrige la signature de fonction
Function func = getFunctionAt(toAddr(0x401000));
func.setReturnType(IntegerDataType.dataType, SourceType.USER_DEFINED);

// Crée un type de structure
StructureDataType struct = new StructureDataType("MyStruct", 0);
struct.add(IntegerDataType.dataType, "field_a", null);
struct.add(PointerDataType.dataType, "next", null);

// S'applique à la mémoire
createData(toAddr(0x601000), struct);

Scripts de correspondance de motifs

# Trouve tous les appels aux fonctions dangereuses
for func in currentProgram.getFunctionManager().getFunctions(True):
    for ref in getReferencesTo(func.getEntryPoint()):
        if func.getName() in ["strcpy", "sprintf", "gets"]:
            print(f"Dangerous call at {ref.getFromAddress()}")

Motifs IDA Pro

Analyse IDAPython

import idaapi
import idautils
import idc

# Trouve tous les appels de fonction
def find_calls(func_name):
    for func_ea in idautils.Functions():
        for head in idautils.Heads(func_ea, idc.find_func_end(func_ea)):
            if idc.print_insn_mnem(head) == "call":
                target = idc.get_operand_value(head, 0)
                if idc.get_func_name(target) == func_name:
                    print(f"Call to {func_name} at {hex(head)}")

# Renomme les fonctions en fonction des chaînes
def auto_rename():
    for s in idautils.Strings():
        for xref in idautils.XrefsTo(s.ea):
            func = idaapi.get_func(xref.frm)
            if func and "sub_" in idc.get_func_name(func.start_ea):
                # Utilise la chaîne comme indice pour nommer
                pass

Bonnes pratiques

Flux de travail d'analyse

  1. Triage initial: Type de fichier, architecture, imports/exports
  2. Analyse des chaînes: Identifie les chaînes intéressantes, messages d'erreur
  3. Identification des fonctions: Points d'entrée, exports, références croisées
  4. Cartographie du flux de contrôle: Comprend la structure du programme
  5. Récupération de structures de données: Identifie les structures, tableaux, variables globales
  6. Identification d'algorithme: Crypto, hachage, compression
  7. Documentation: Commentaires, symboles renommés, définitions de type

Pièges courants

  • Artefacts d'optimiseur: Le code peut ne pas correspondre à la structure source
  • Fonctions inline: Les fonctions peuvent être développées inline
  • Optimisation des appels terminaux: jmp au lieu de call + ret
  • Code mort: Code inaccessible suite à l'optimisation
  • Code indépendant de la position: Adressage RIP-relatif

Skills similaires