Low-Level Software : structures, langages, mémoire et représentation bas niveau

Salut ce forum est dédié à comprendre les entrailles de l'ordinateur au plus bas niveau bienvenue dans le monde du reverse et de l'assembleur

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 114
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Low-Level Software : structures, langages, mémoire et représentation bas niveau

Post by Hydraxx »

Comprendre comment le C/C++ devient de l’assembleur — bases pratiques pour le reverse

Objectif du cours

Ce cours a un objectif très concret :

Code: Select all

prendre du code C/C++
↓
voir comment il apparaît en assembleur
↓
apprendre à le reconnaître dans x64dbg
↓
reconstruire la logique du programme
Le but n’est pas de mémoriser des centaines de définitions.

Le but est d’apprendre à reconnaître des motifs.

Quand tu vois :

Code: Select all

mov eax, [rcx+8]
tu dois progressivement penser :

Code: Select all

"on lit probablement un champ dans une structure ou un objet"
Quand tu vois :

Code: Select all

cmp eax, 10
jge ...
tu dois penser :

Code: Select all

"il y a probablement une condition ou une limite de boucle"
Quand tu vois :

Code: Select all

sub rsp, 40h
tu dois penser :

Code: Select all

"la fonction réserve de la place sur la stack"
C’est cette capacité de traduction qui est fondamentale en reverse engineering.

1. La vision haut niveau et la vision bas niveau

Un développeur écrit :

Code: Select all

int x = 10;
int y = 20;
int z = x + y;
Le processeur ne connaît pas les noms `x`, `y` ou `z`.

Il manipule seulement :
  • des registres ;
  • des adresses mémoire ;
  • des valeurs ;
  • des instructions.
Une version assembleur possible serait :

Code: Select all

mov dword ptr [rsp+20h], 10
mov dword ptr [rsp+24h], 20

mov eax, dword ptr [rsp+20h]
add eax, dword ptr [rsp+24h]

mov dword ptr [rsp+28h], eax
Mentalement, tu peux reconstruire :

Code: Select all

[rsp+20h] = x
[rsp+24h] = y
[rsp+28h] = z
Ce qu’il faut regarder dans x64dbg

Si tu vois une instruction comme :

Code: Select all

mov eax, dword ptr [rsp+20h]
tu peux :
  • regarder la valeur de RSP ;
  • calculer mentalement RSP + 0x20 ;
  • suivre cette adresse dans la vue dump ;
  • observer la valeur ;
  • voir quelles autres instructions utilisent la même adresse.
Mini défi

Que représente probablement ceci ?

Code: Select all

mov eax, [rsp+20h]
add eax, 5
mov [rsp+20h], eax
Réponse logique :

Code: Select all

une variable locale est lue,
augmentée de 5,
puis réécrite
En C/C++ :

Code: Select all

x += 5;
2. Variables : registre, stack, mémoire globale

Une variable C++ peut être stockée à différents endroits.

Cas 1 : variable dans un registre

Code :

Code: Select all

int x = 10;
return x;
Le compilateur peut produire :

Code: Select all

mov eax, 10
ret
Ici, aucune place mémoire n’est nécessaire.

La valeur reste directement dans `EAX`.

Cas 2 : variable sur la stack

Code :

Code: Select all

int x = 10;
int y = x + 2;
Version possible :

Code: Select all

sub rsp, 30h

mov dword ptr [rsp+20h], 10
mov eax, dword ptr [rsp+20h]
add eax, 2
mov dword ptr [rsp+24h], eax

add rsp, 30h
ret
On peut représenter la stack ainsi :

Code: Select all

RSP
│
├── +00h  espace réservé
├── +08h
├── +10h
├── +18h
├── +20h  x = 10
├── +24h  y = 12
│
Cas 3 : variable globale

Code :

Code: Select all

int g_Count = 5;
En assembleur, on peut voir :

Code: Select all

mov eax, dword ptr [g_Count]
ou un accès relatif à RIP :

Code: Select all

mov eax, dword ptr [rip+1234h]
Dans un binaire 64 bits, les variables globales sont très souvent accessibles via des adresses relatives à `RIP`.

À reconnaître en reverse
  • `[rsp+offset]` → souvent variable locale ;
  • `[rbp-offset]` → souvent variable locale dans certaines fonctions ;
  • `[rip+offset]` → souvent global, constante ou donnée de section ;
  • registre seul → valeur temporaire ou variable optimisée.
3. Structures : comprendre les offsets

Prenons :

Code: Select all

struct Player
{
    int hp;
    int mana;
};
En mémoire :

Code: Select all

Player
+0x00 → hp
+0x04 → mana
Code :

Code: Select all

Player p;
p.hp = 100;
p.mana = 50;
En assembleur :

Code: Select all

mov dword ptr [rcx], 100
mov dword ptr [rcx+4], 50
Ici, `RCX` contient probablement l’adresse du `Player`.

Tu peux lire :

Code: Select all

[rcx]   → champ offset 0
[rcx+4] → champ offset 4
Exemple plus réaliste

Code: Select all

struct Session
{
    int id;
    int state;
    void* context;
};
Sur x64 :

Code: Select all

+0x00 → id
+0x04 → state
+0x08 → context
Code assembleur possible :

Code: Select all

mov eax, [rcx]
cmp dword ptr [rcx+4], 1
jne inactive

mov rax, [rcx+8]
Tu peux reconstruire :

Code: Select all

session->id
session->state
session->context
Règle pratique

Si plusieurs instructions utilisent :

Code: Select all

[rcx]
[rcx+4]
[rcx+8]
[rcx+10h]
avec le même registre de base, pense immédiatement :

Code: Select all

structure
objet
tableau de champs
contexte de fonction
Mini défi

Code: Select all

mov eax, [rdx+8]
add eax, 10
mov [rdx+8], eax
Interprétation probable :

Code: Select all

un champ situé à l’offset +8
est lu,
augmenté de 10,
puis réécrit
En C++ :

Code: Select all

obj->field += 10;
4. Pointeurs : adresse contre valeur

En reverse, beaucoup d’erreurs viennent de la confusion entre :

Code: Select all

adresse
et
valeur contenue à cette adresse
Exemple C++ :

Code: Select all

int x = 10;
int* p = &x;
Supposons :

Code: Select all

x est à l’adresse 0000000000100000
Alors :

Code: Select all

p = 0000000000100000
*p = 10
En assembleur :

Code: Select all

mov rax, p
mov ecx, [rax]
Ici :

Code: Select all

rax = adresse
[rax] = contenu de l’adresse
Très important dans x64dbg

Si `RAX` vaut :

Code: Select all

000001F4A1200000
cela ne veut pas dire que la valeur logique du programme est ce grand nombre.

Cela peut être une adresse.

Tu dois vérifier :
  • si cette adresse appartient à une zone mémoire valide ;
  • ce qu’elle contient ;
  • si elle pointe vers une chaîne ;
  • si elle pointe vers une structure ;
  • si d’autres instructions utilisent `[rax+offset]`.
5. Tableaux

Code C++ :

Code: Select all

int values[4] = {10, 20, 30, 40};
Chaque `int` fait 4 octets.

En mémoire :

Code: Select all

base+0x00 → 10
base+0x04 → 20
base+0x08 → 30
base+0x0C → 40
Accès :

Code: Select all

values[i]
peut devenir :

Code: Select all

mov eax, [rcx+rdx*4]
Ici :

Code: Select all

RCX = adresse du tableau
RDX = index
*4  = taille d’un int
C’est un motif extrêmement important.

Reconnaître la taille des éléments

Code: Select all

[base + index*1] → éléments de 1 octet
[base + index*2] → éléments de 2 octets
[base + index*4] → éléments de 4 octets
[base + index*8] → éléments de 8 octets
Mini défi

Code: Select all

mov eax, [rcx+r8*4]
Interprétation probable :

Code: Select all

lecture d’un tableau d’éléments de 4 octets
à l’index R8
6. Fonctions et paramètres

Prenons :

Code: Select all

int Add(int a, int b)
{
    return a + b;
}
Sous Windows x64, les premiers arguments entiers/pointeurs passent généralement par :

Code: Select all

RCX
RDX
R8
R9
Donc :

Code: Select all

int Add(int a, int b)
peut devenir :

Code: Select all

RCX = a
RDX = b
et la fonction :

Code: Select all

mov eax, ecx
add eax, edx
ret
La valeur de retour entière revient généralement dans :

Code: Select all

EAX / RAX
Exemple avec appel

C++ :

Code: Select all

int result = Add(10, 20);
Assembleur possible :

Code: Select all

mov ecx, 10
mov edx, 20
call Add
mov [rsp+20h], eax
Lecture mentale :

Code: Select all

argument 1 = 10
argument 2 = 20
appel
résultat dans eax
stockage dans variable locale
Mini défi

Code: Select all

mov ecx, 100
mov edx, 5
call sub_140001000
Sans même connaître la fonction, tu sais déjà :

Code: Select all

elle reçoit probablement au moins 2 arguments
arg1 = 100
arg2 = 5
7. `call` et `ret`

Une fonction est généralement appelée avec :

Code: Select all

call adresse
Le processeur mémorise l’adresse à laquelle il devra revenir.

Puis le code de la fonction s’exécute.

À la fin :

Code: Select all

ret
ramène l’exécution dans la fonction appelante.

Schéma :

Code: Select all

main
 |
 | call Function
 v
Function
 |
 | ret
 v
main continue ici
Dans x64dbg

Deux commandes sont importantes :
  • Step Into : entrer dans la fonction appelée ;
  • Step Over : exécuter l’appel sans entrer dedans.
Quand tu veux comprendre une API ou une fonction interne importante :

Code: Select all

Step Into
Quand tu ne veux pas perdre du temps dans `std::cout`, runtime C++, etc. :

Code: Select all

Step Over
8. Conditions : reconnaître un if

Code :

Code: Select all

if (x == 10)
{
    success();
}
Assembleur possible :

Code: Select all

cmp eax, 10
jne skip

call success

skip:
La logique :

Code: Select all

cmp eax, 10
compare.

Puis :

Code: Select all

jne skip
saute si les valeurs sont différentes.

Donc l’appel n’est exécuté que si :

Code: Select all

eax == 10
Autre exemple

C++ :

Code: Select all

if (x > 100)
{
    High();
}
else
{
    Low();
}
Assembleur :

Code: Select all

cmp eax, 100
jle low

call High
jmp end_if

low:
call Low

end_if:
Tu peux reconstruire le `if/else`.

9. `test` : très fréquent en reverse

`test` réalise un ET logique uniquement pour modifier les flags.

Très souvent :

Code: Select all

test rax, rax
je failed
veut dire :

Code: Select all

if (rax == 0)
    goto failed;
C’est courant pour tester :
  • un pointeur nul ;
  • un handle nul ;
  • un résultat égal à zéro ;
  • un booléen.
Exemple :

Code: Select all

void* p = malloc(100);

if (!p)
    return;
peut produire :

Code: Select all

call malloc
test rax, rax
je allocation_failed
Réflexe à développer

Quand tu vois :

Code: Select all

test reg, reg
pense :

Code: Select all

"on teste probablement si cette valeur vaut zéro"
10. Boucles

Code :

Code: Select all

for (int i = 0; i < 5; i++)
{
    DoWork();
}
Version assembleur possible :

Code: Select all

xor ebx, ebx

loop_start:

cmp ebx, 5
jge loop_end

call DoWork

inc ebx
jmp loop_start

loop_end:
Le signe révélateur d’une boucle est souvent :

Code: Select all

un saut vers une adresse située plus haut
Exemple :

Code: Select all

0000000140001020 cmp eax, 5
0000000140001023 jge 140001035
...
0000000140001032 jmp 140001020
Le dernier `jmp` revient vers le début.

Tu peux donc soupçonner une boucle.

Boucle sur une chaîne

C :

Code: Select all

while (*p != '\0')
{
    p++;
}
Assembleur possible :

Code: Select all

loop_start:
mov al, [rcx]
test al, al
je done

inc rcx
jmp loop_start

done:
Ici :

Code: Select all

test al, al
teste si le caractère vaut zéro.

Le zéro final correspond au terminateur :

Code: Select all

'\0'
C’est exactement le genre de motif que tu peux voir dans `strlen`, `strcpy`, etc.

11. La stack

La stack est essentielle en reverse.

Elle sert notamment à stocker :
  • variables locales ;
  • adresses de retour ;
  • registres sauvegardés ;
  • arguments supplémentaires ;
  • données temporaires.
Prenons :

Code: Select all

int Function()
{
    int a = 10;
    int b = 20;
    return a + b;
}
Version possible :

Code: Select all

sub rsp, 30h

mov dword ptr [rsp+20h], 10
mov dword ptr [rsp+24h], 20

mov eax, [rsp+20h]
add eax, [rsp+24h]

add rsp, 30h
ret
Le :

Code: Select all

sub rsp, 30h
réserve 0x30 octets.

Le :

Code: Select all

add rsp, 30h
les libère logiquement avant le retour.

Attention

`sub rsp` ne fait pas un `VirtualAlloc`.

La stack existe déjà.

On déplace simplement `RSP` afin de réserver une partie de cette stack pour la fonction.

Schéma

Code: Select all

adresse haute
      |
      | anciennes données
      |
      | adresse de retour
      |
      | variable b
      | variable a
RSP ->| zone actuelle de la fonction
      |
adresse basse
La pile grandit vers les adresses plus basses.

12. `push` et `pop`

`push` place une valeur sur la pile.

Exemple :

Code: Select all

push rbx
Simplifié :

Code: Select all

rsp = rsp - 8
[rsp] = rbx
`pop` récupère une valeur.

Code: Select all

pop rbx
Simplifié :

Code: Select all

rbx = [rsp]
rsp = rsp + 8
Très souvent :

Code: Select all

push rbx
...
pop rbx
ret
signifie que la fonction sauvegarde `RBX`, l’utilise, puis restaure sa valeur avant de revenir.

Réflexe

Ne pense pas :

Code: Select all

push = allocation mémoire générale
Pense plutôt :

Code: Select all

push = stocker temporairement une valeur sur la stack
13. Le tas (heap)

Le heap sert aux allocations dynamiques.

Exemple :

Code: Select all

int* p = new int(100);
ou :

Code: Select all

int* p = (int*)malloc(sizeof(int));
*p = 100;
Au niveau bas, on obtient une adresse vers une zone allouée.

Exemple conceptuel :

Code: Select all

call malloc
test rax, rax
je failed

mov dword ptr [rax], 100
Ici :

Code: Select all

RAX = pointeur vers la nouvelle allocation
[RAX] = contenu
Sous Windows, tu peux rencontrer :

Code: Select all

HeapAlloc
VirtualAlloc
LocalAlloc
CoTaskMemAlloc
operator new
Exemple avec VirtualAlloc

C++ :

Code: Select all

void* p = VirtualAlloc(
    nullptr,
    0x1000,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE
);
Juste avant l’appel sous Windows x64, tu peux retrouver les arguments dans :

Code: Select all

RCX = nullptr
RDX = 0x1000
R8  = 0x3000
R9  = 0x04
Puis :

Code: Select all

call VirtualAlloc
et ensuite :

Code: Select all

RAX = adresse retournée
C’est exactement le genre de reverse pratique très utile.

14. Exemple pratique : reconnaître une allocation

Tu vois dans x64dbg :

Code: Select all

xor ecx, ecx
mov edx, 1000h
mov r8d, 3000h
mov r9d, 4
call qword ptr [VirtualAlloc]
Tu peux immédiatement traduire :

Code: Select all

VirtualAlloc(
    NULL,
    0x1000,
    MEM_COMMIT | MEM_RESERVE,
    PAGE_READWRITE
)
Pourquoi ?

Code: Select all

0x1000 = taille
0x3000 = MEM_COMMIT | MEM_RESERVE
0x4    = PAGE_READWRITE
Puis si après l’appel :

Code: Select all

mov rbx, rax
alors :

Code: Select all

RBX garde probablement le pointeur retourné
15. Objets C++

Prenons :

Code: Select all

class Player
{
public:
    int hp;

    void Damage(int amount)
    {
        hp -= amount;
    }
};
Appel :

Code: Select all

player.Damage(20);
Sous Windows x64, une méthode d’instance reçoit souvent :

Code: Select all

RCX = this
et l’autre argument peut être :

Code: Select all

RDX = 20
Dans la méthode :

Code: Select all

mov eax, [rcx]
sub eax, edx
mov [rcx], eax
ret
Tu peux reconstruire :

Code: Select all

this->hp -= amount;
Réflexe

Si une fonction reçoit un pointeur dans `RCX` et fait énormément de :

Code: Select all

[rcx+offset]
il est possible que `RCX` soit :

Code: Select all

this
16. Fonctions virtuelles et vtables

En C++, le polymorphisme peut faire apparaître des appels indirects.

Exemple :

Code: Select all

obj->Run();
peut devenir :

Code: Select all

mov rax, [rcx]
call qword ptr [rax+10h]
Interprétation :

Code: Select all

[rcx] = pointeur vers la vtable
[rax+10h] = entrée de méthode virtuelle
call [...] = appel indirect
En reverse C++, ce motif est très important.

17. Listes chaînées

Code :

Code: Select all

struct Node
{
    int value;
    Node* next;
};
Parcours :

Code: Select all

while (node)
{
    std::cout << node->value;
    node = node->next;
}
Version bas niveau possible :

Code: Select all

loop:
test rcx, rcx
je done

mov eax, [rcx]
; utilisation de value

mov rcx, [rcx+8]
jmp loop

done:
Tu peux reconnaître :

Code: Select all

[rcx]   = value
[rcx+8] = next
Le pointeur se remplace par le champ `next`.

C’est typiquement une structure chaînée.

18. Sections mémoire d’un programme

Un exécutable contient plusieurs catégories de données.

En simplifiant :

Code: Select all

.text   → code exécutable
.rdata  → données en lecture seule
.data   → données globales initialisées
.bss    → données globales non initialisées
Sous Windows, un PE peut avoir plusieurs autres sections.

Pour toi, au début, retiens surtout :

Code: Select all

.text  = code
.rdata = chaînes / constantes
.data  = globals modifiables
Exemple pratique

Si tu cherches :

Code: Select all

"Access denied"
et que la chaîne se trouve dans `.rdata`, tu peux suivre ses références.

Une référence peut t’amener à :

Code: Select all

cmp eax, 1
je success

lea rcx, [rip+AccessDenied]
call Print
Tu viens de retrouver une partie de la logique du programme à partir d’une chaîne.

19. Imports : excellent point d’entrée

Les imports donnent beaucoup d’informations.

Exemples :

Code: Select all

CreateFileW
ReadFile
WriteFile
VirtualAlloc
HeapAlloc
RegOpenKeyExW
socket
connect
CreateProcessW
OpenProcessToken
Avant même de lire l’assembleur, ils te donnent une idée des capacités du programme.

Exemple :

Code: Select all

CreateFileW
ReadFile
CryptDecrypt
WriteFile
Cela peut suggérer :

Code: Select all

ouvrir un fichier
↓
lire des données
↓
traiter/déchiffrer
↓
écrire un résultat
Technique pratique

Dans x64dbg :
  • ouvre les imports ;
  • trouve une API intéressante ;
  • place un breakpoint dessus ;
  • lance le programme ;
  • quand le breakpoint se déclenche, regarde les registres arguments ;
  • remonte dans l’appelant.
C’est une technique extrêmement efficace.

20. Exemple pratique complet : structure + fonction + condition

Code source :

Code: Select all

struct Session
{
    int id;
    bool active;
};

int CheckSession(Session* s)
{
    if (!s->active)
        return 0;

    return s->id + 5;
}
Version assembleur simplifiée :

Code: Select all

CheckSession:

cmp byte ptr [rcx+4], 0
je inactive

mov eax, dword ptr [rcx]
add eax, 5
ret

inactive:
xor eax, eax
ret
Analyse étape par étape.

Étape 1

Code: Select all

cmp byte ptr [rcx+4], 0
On lit un octet à l’offset +4.

Cela ressemble à :

Code: Select all

bool
char
petit champ de structure
Puis :

Code: Select all

je inactive
Si ce champ vaut zéro, on saute.

On peut suspecter :

Code: Select all

if (!something)
Étape 2

Code: Select all

mov eax, dword ptr [rcx]
On lit un entier 32 bits à l’offset 0.

Probablement un autre champ.

Étape 3

Code: Select all

add eax, 5
On ajoute 5 à ce champ.

Étape 4

Code: Select all

ret
Comme `EAX` contient le résultat, la fonction retourne cette valeur.

Étape 5

Branche inactive :

Code: Select all

xor eax, eax
ret
`xor eax, eax` met `EAX` à zéro.

Donc :

Code: Select all

return 0;
On peut reconstruire :

Code: Select all

if (*(byte*)(rcx + 4) == 0)
    return 0;

return *(int*)(rcx + 0) + 5;
Puis donner des noms :

Code: Select all

if (!s->active)
    return 0;

return s->id + 5;
C’est exactement le raisonnement du reverse engineer.

21. Exemple pratique complet : boucle sur tableau

Code :

Code: Select all

int Sum(int* values, int count)
{
    int total = 0;

    for (int i = 0; i < count; i++)
        total += values[i];

    return total;
}
Assembleur simplifié :

Code: Select all

xor eax, eax
xor r8d, r8d

loop_start:

cmp r8d, edx
jge done

add eax, dword ptr [rcx+r8*4]

inc r8d
jmp loop_start

done:
ret
Analyse :

Code: Select all

RCX = values
EDX = count
R8D = i
EAX = total
Puis :

Code: Select all

[rcx+r8*4]
veut dire :

Code: Select all

values[i]
Tu reconnais :
  • un tableau ;
  • un index ;
  • une taille de 4 octets ;
  • une boucle ;
  • un accumulateur.
22. Exemple pratique complet : chaîne de caractères

Code :

Code: Select all

int Length(const char* s)
{
    int len = 0;

    while (*s)
    {
        len++;
        s++;
    }

    return len;
}
Assembleur simplifié :

Code: Select all

xor eax, eax

loop_start:

cmp byte ptr [rcx], 0
je done

inc eax
inc rcx

jmp loop_start

done:
ret
Interprétation :

Code: Select all

RCX = pointeur sur la chaîne
EAX = longueur
Puis :

Code: Select all

cmp byte ptr [rcx], 0
teste le caractère courant.

Si zéro :

Code: Select all

fin de chaîne
Puis :

Code: Select all

inc rcx
avance d’un octet.

Donc tu reconnais une boucle sur une chaîne C.

23. Exemple pratique complet : appel d’API Windows

Code :

Code: Select all

HANDLE h = CreateFileW(
    path,
    GENERIC_READ,
    FILE_SHARE_READ,
    nullptr,
    OPEN_EXISTING,
    0,
    nullptr
);
Tu places un breakpoint sur :

Code: Select all

CreateFileW
Au moment du breakpoint :

Code: Select all

RCX = lpFileName
RDX = dwDesiredAccess
R8  = dwShareMode
R9  = lpSecurityAttributes
Les arguments suivants sont sur la stack selon la convention d’appel Windows x64.

Tu peux suivre `RCX` dans le dump.

Si tu vois :

Code: Select all

C:\test\data.bin
tu viens de retrouver le fichier que le programme tente d’ouvrir.

Cette technique est souvent bien plus rapide que de lire tout le programme depuis le début.

24. Comment travailler dans x64dbg

Pour chaque morceau intéressant :

1. Repère l’entrée

Utilise :
  • imports ;
  • chaînes ;
  • API ;
  • breakpoints ;
  • références.
2. Observe les registres

Regarde :

Code: Select all

RAX
RBX
RCX
RDX
R8
R9
RSP
RBP
RIP
3. Suis les pointeurs

Si un registre ressemble à une adresse valide :

Code: Select all

clic droit
→ suivre dans le dump
4. Note les offsets

Si tu vois :

Code: Select all

[rcx+8]
[rcx+10h]
[rcx+18h]
écris mentalement :

Code: Select all

structure probable
5. Repère les branches

Cherche :

Code: Select all

cmp
test
je
jne
jg
jl
jge
jle
6. Repère les boucles

Cherche les sauts vers une adresse antérieure.

7. Repère les appels

Regarde les arguments juste avant :

Code: Select all

call
8. Reconstruis du pseudo-code

Même très approximatif.

Exemple :

Code: Select all

if (field == 0)
    return 0;

return other_field + 5;
C’est déjà suffisant.

25. Erreurs classiques du débutant

Erreur 1 : vouloir comprendre chaque instruction

Inutile.

Cherche d’abord les motifs importants.

Erreur 2 : confondre adresse et valeur

Toujours vérifier si un registre pointe vers une zone mémoire.

Erreur 3 : croire que le pseudo-code est exact

Un décompilateur reconstruit une approximation.

Toujours vérifier l’assembleur quand quelque chose paraît étrange.

Erreur 4 : ignorer les appels d’API

Les API sont souvent les meilleurs points de repère.

Erreur 5 : oublier les optimisations

Un code Release peut être très différent du C++ original.

Exemple :

Code: Select all

int x = 5;
return x * 2;
peut simplement devenir :

Code: Select all

mov eax, 10
ret
La multiplication n’existe plus.

26. Méthode de traduction mentale

Quand tu vois :

Code: Select all

mov eax, [rcx+8]
pense :

Code: Select all

field = object->field;
Quand tu vois :

Code: Select all

mov [rcx+8], eax
pense :

Code: Select all

object->field = value;
Quand tu vois :

Code: Select all

test rax, rax
je ...
pense :

Code: Select all

if (!ptr)
Quand tu vois :

Code: Select all

cmp eax, 10
jne ...
pense :

Code: Select all

if (x != 10)
Quand tu vois :

Code: Select all

call Function
regarde immédiatement :

Code: Select all

RCX
RDX
R8
R9
Quand tu vois :

Code: Select all

[rcx+r8*4]
pense :

Code: Select all

tableau[index]
Quand tu vois :

Code: Select all

sub rsp, ...
pense :

Code: Select all

stack frame / variables locales
27. Mini exercices

Exercice 1

Code: Select all

mov eax, [rcx+4]
sub eax, 1
mov [rcx+4], eax
Question :

Que fait probablement ce code ?

Réponse :

Code: Select all

un champ à l’offset +4 est décrémenté
C++ probable :

Code: Select all

obj->field--;
Exercice 2

Code: Select all

test rcx, rcx
je failed
Réponse :

Code: Select all

if (rcx == nullptr)
    goto failed;
Exercice 3

Code: Select all

cmp eax, edx
jge done
inc eax
jmp loop
Réponse :

Code: Select all

probablement une boucle avec une condition de type :
i < limit
Exercice 4

Code: Select all

mov eax, [rcx+r8*4]
Réponse :

Code: Select all

lecture probable d’un int dans un tableau
Exercice 5

Code: Select all

mov rcx, rbx
mov edx, 20
call sub_140001000
Réponse :

Code: Select all

fonction appelée avec :
arg1 = RBX
arg2 = 20
28. Défi pratique complet

Voici un code C++ :

Code: Select all

struct Player
{
    int hp;
    int armor;
};

int Hit(Player* p, int damage)
{
    int realDamage = damage - p->armor;

    if (realDamage < 0)
        realDamage = 0;

    p->hp -= realDamage;

    if (p->hp < 0)
        p->hp = 0;

    return p->hp;
}
Imagine que tu trouves :

Code: Select all

mov eax, edx
sub eax, dword ptr [rcx+4]

test eax, eax
jge damage_ok

xor eax, eax

damage_ok:

sub dword ptr [rcx], eax

cmp dword ptr [rcx], 0
jge hp_ok

mov dword ptr [rcx], 0

hp_ok:

mov eax, dword ptr [rcx]
ret
Tu peux reconstruire :

Code: Select all

[rcx]   = hp
[rcx+4] = armor
edx     = damage
eax     = realDamage / résultat
Puis :

Code: Select all

damage - armor
↓
si négatif → 0
↓
hp -= damage réel
↓
si hp négatif → 0
↓
return hp
Voilà exactement ce que tu dois apprendre à faire.

29. Ce qu’il faut retenir

Le cœur du chapitre tient dans cette idée :

Code: Select all

le reverse engineering consiste à reconstruire
les concepts haut niveau
à partir de leur représentation bas niveau
Les réflexes les plus importants :
  • `[rsp+offset]` → variable locale probable ;
  • `[registre+offset]` → structure ou objet probable ;
  • `[base+index*taille]` → tableau probable ;
  • `test reg, reg` → test de zéro probable ;
  • `cmp + jcc` → condition probable ;
  • saut vers le haut → boucle probable ;
  • `call` → regarder les arguments ;
  • `eax/rax` après appel → valeur de retour probable ;
  • `sub rsp` → réservation de stack ;
  • `VirtualAlloc`, `HeapAlloc`, `malloc`, `new` → allocation dynamique.
Le plus important n’est pas de réciter les instructions.

Il faut arriver à regarder :

Code: Select all

cmp byte ptr [rcx+4], 0
je failed
mov eax, [rcx]
add eax, 5
ret
et penser :

Code: Select all

"on teste un champ booléen dans une structure,
puis on retourne un autre champ + 5"
À ce moment-là, tu ne lis plus simplement de l’assembleur.

Tu fais réellement du reverse engineering.

Who is online

Users browsing this forum: No registered users and 1 guest