Les tableaux sont des zones de mémoire contiguës contenant plusieurs éléments de même type.
En assembleur, il n'existe pas directement une abstraction de haut niveau appelée "tableau" comme en C ou C++. On manipule simplement :
Code: Select all
une adresse de base
un index
une taille d'élément
Code: Select all
adresse_element = base + index * taille_element
1. Structure d'un tableau en mémoire
Prenons :
Code: Select all
int tableau[4] = {10, 20, 30, 40};
En mémoire :
Code: Select all
adresse base + 0 -> 10
adresse base + 4 -> 20
adresse base + 8 -> 30
adresse base + 12 -> 40
Code: Select all
tableau[i]
Code: Select all
base + i * 4
Comme en C/C++, le premier élément est généralement :
Code: Select all
index 0
Code: Select all
tableau[0]
tableau[1]
tableau[2]
...
Code: Select all
tableau[0] -> base + 0
tableau[1] -> base + 4
tableau[2] -> base + 8
tableau[3] -> base + 12
L'adressage x86-64 permet une forme très puissante :
Code: Select all
[base + index*scale + displacement]
Code: Select all
base = adresse de départ
index = numéro d'élément
scale = taille multiplicative
displacement = constante ajoutée
Code: Select all
[rbx + rcx*4 + 8]
Code: Select all
adresse = RBX + RCX*4 + 8
Le facteur d'échelle matériel peut être :
Code: Select all
1
2
4
8
Code: Select all
1 octet -> byte
2 octets -> word
4 octets -> dword
8 octets -> qword
Code: Select all
[rax + rcx] ; éléments de 1 octet
[rax + rcx*2] ; éléments de 2 octets
[rax + rcx*4] ; éléments de 4 octets
[rax + rcx*8] ; éléments de 8 octets
Exemple :
Code: Select all
char buffer[100];
Un accès peut ressembler à :
Code: Select all
mov al, [rbx + rcx]
Code: Select all
RBX = base du tableau
RCX = index
6. Tableau de words
Pour un tableau de valeurs de 2 octets :
Code: Select all
mov ax, [rbx + rcx*2]
Code: Select all
taille_element = 2
Pour un tableau de `int` 32 bits :
Code: Select all
mov eax, [rbx + rcx*4]
On peut le lire directement comme :
Code: Select all
EAX = tableau[RCX]
8. Tableau de qwords
Pour des éléments de 8 octets :
Code: Select all
mov rax, [rbx + rcx*8]
- un tableau de `uint64_t` ;
- un tableau de `long long` ;
- un tableau de pointeurs.
9. Exemple concret de calcul d'adresse
Supposons :
Code: Select all
base = 1000h
index = 3
taille_element = 4
Code: Select all
adresse = 1000h + 3*4
= 1000h + 0Ch
= 100Ch
Code: Select all
tableau[3]
Code: Select all
100Ch
On peut aussi rencontrer :
Code: Select all
[rbx + rcx*4 + 10h]
Code: Select all
base + index*4 + 0x10
- un champ situé dans une structure ;
- une sous-zone ;
- un offset fixe avant le tableau ;
- un membre de structure contenant un tableau.
`lea` est très utile pour calculer des adresses.
Exemple :
Code: Select all
lea rax, [rbx + rcx*4]
Code: Select all
RBX + RCX*4
Différence :
Code: Select all
lea rax, [rbx + rcx*4]
Code: Select all
mov eax, [rbx + rcx*4]
Cette différence est fondamentale en reverse.
12. Parcourir un tableau
Un parcours classique ressemble à :
Code: Select all
xor ecx, ecx
loop:
mov eax, [rbx + rcx*4]
inc rcx
cmp rcx, 10
jl loop
Code: Select all
for (int i = 0; i < 10; i++)
{
eax = tableau[i];
}
Exemple :
Code: Select all
xor ecx, ecx
loop:
mov eax, [rbx + rcx*4]
add eax, 1
mov [rbx + rcx*4], eax
inc rcx
cmp rcx, 10
jl loop
Code: Select all
for (int i = 0; i < 10; i++)
{
tableau[i]++;
}
Supposons :
Code: Select all
RBX = source
RDX = destination
RCX = index
Code: Select all
mov eax, [rbx + rcx*4]
mov [rdx + rcx*4], eax
Code: Select all
destination[i] = source[i]
15. Tableau passé à une fonction
En assembleur, une fonction reçoit généralement l'adresse du premier élément du tableau.
Exemple C :
Code: Select all
void process(int* array, size_t count);
Code: Select all
RCX = adresse du tableau
RDX = nombre d'éléments
Code: Select all
[rcx + index*4]
16. Tableau et pointeur
Au niveau assembleur, un tableau est essentiellement manipulé via une adresse.
Donc :
Code: Select all
int array[10]
Code: Select all
int* ptr
Code: Select all
base + index * taille
17. Allocation dynamique d'un tableau
Pour allouer dynamiquement un tableau :
Code: Select all
nombre_elements * taille_element
Code: Select all
100 int
```
nécessitent :
[code]
100 * 4 = 400 octets
Code: Select all
int* array = malloc(100 * sizeof(int));
Après l'appel :
Code: Select all
RAX = pointeur retourné
18. Vérifier le retour de malloc
Après :
Code: Select all
call malloc
Code: Select all
test rax, rax
je allocation_failed
Code: Select all
if (rax == NULL)
allocation_failed;
19. Libération avec free
Une mémoire allouée dynamiquement doit être libérée lorsqu'elle n'est plus nécessaire.
En C :
Code: Select all
free(array);
Code: Select all
RCX = pointeur
call free
20. Remplir un tableau
Exemple :
Code: Select all
xor ecx, ecx
fill_loop:
mov eax, ecx
mov [rbx + rcx*4], eax
inc rcx
cmp rcx, 10
jl fill_loop
Code: Select all
array[0] = 0
array[1] = 1
array[2] = 2
...
array[9] = 9
Une fonction peut appeler un générateur aléatoire puis stocker le résultat dans chaque élément.
Conceptuellement :
Code: Select all
for (i = 0; i < count; i++)
{
array[i] = rand();
}
Code: Select all
call rand
mov [base + index*4], eax
22. Afficher les éléments
Pour afficher un tableau, une boucle lit un élément puis appelle une fonction comme `printf`.
Conceptuellement :
Code: Select all
for (i = 0; i < count; i++)
{
printf("%d", array[i]);
}
Code: Select all
adresse du format
valeur courante
appel à printf
index
Un algorithme classique :
Code: Select all
min = array[0];
for (i = 1; i < count; i++)
{
if (array[i] < min)
min = array[i];
}
Code: Select all
mov eax, [rbx] ; min = array[0]
mov ecx, 1
loop:
mov edx, [rbx + rcx*4]
cmp edx, eax
jge no_change
mov eax, edx
no_change:
inc rcx
...
Même logique :
Code: Select all
max = array[0];
for (...)
{
if (array[i] > max)
max = array[i];
}
25. Alignement des tableaux
Les données peuvent être alignées sur certaines frontières.
Exemples :
Code: Select all
align 4
align 8
align 16
Il peut aussi introduire du padding entre certaines zones de données.
Pour un simple reverse de code user mode, retiens surtout que les adresses peuvent ne pas être immédiatement consécutives entre plusieurs objets distincts à cause de l'alignement.
26. argv est un tableau de pointeurs
En C :
Code: Select all
int main(int argc, char** argv)
Conceptuellement :
Code: Select all
argv[0] -> pointeur vers le nom du programme
argv[1] -> pointeur vers le premier argument
argv[2] -> pointeur vers le deuxième argument
...
Donc un accès à :
Code: Select all
argv[i]
Code: Select all
mov rax, [rbx + rcx*8]
27. Reconnaître un tableau en reverse
Une instruction comme :
Code: Select all
mov eax, [rbx + rcx*4]
Code: Select all
RBX = base probable
RCX = index probable
4 = taille probable d'un élément
De même :
Code: Select all
mov rax, [rbx + rcx*8]
28. Attention : scale ne prouve pas le type exact
Voir :
Code: Select all
*4
Cela signifie seulement que l'index est multiplié par 4.
Le type réel peut être :
- un entier 32 bits ;
- un float ;
- une structure de 4 octets ;
- une partie d'un calcul d'adresse.
29. Tableau de structures
Une structure peut faire une taille différente de 1, 2, 4 ou 8 octets.
Exemple :
Code: Select all
struct Item
{
int a;
int b;
int c;
};
Code: Select all
12 octets
Code: Select all
imul rax, rcx, 12
lea rdx, [rbx + rax]
En reverse, tous les tableaux ne se reconnaissent donc pas uniquement à `*4` ou `*8`.
30. Exemple typique de reverse
Supposons :
Code: Select all
xor ecx, ecx
loop:
mov eax, [rbx + rcx*4]
add eax, 1
mov [rbx + rcx*4], eax
inc rcx
cmp rcx, 10
jl loop
Code: Select all
for (int i = 0; i < 10; i++)
{
array[i] += 1;
}
Code: Select all
RCX démarre à 0
RCX est incrémenté
comparaison avec 10
accès [RBX + RCX*4]
- un tableau est une suite d'éléments contigus ;
- la formule clé est `base + index * taille_element` ;
- la forme x86-64 classique est `[base + index*scale + displacement]` ;
- scale vaut 1, 2, 4 ou 8 ;
- `[rax+rcx*4]` est un motif typique d'accès à un élément de 4 octets ;
- `[rax+rcx*8]` est fréquent pour des pointeurs ou valeurs 64 bits ;
- `lea` calcule une adresse sans lire la mémoire ;
- un tableau passé à une fonction est généralement représenté par un pointeur ;
- le nombre d'éléments est souvent passé séparément ;
- `malloc` retourne généralement le pointeur dans RAX ;
- il faut tester le retour de malloc ;
- `free` libère une allocation dynamique ;
- `argv` est un tableau de pointeurs ;
- le scale permet de deviner une taille probable, pas forcément le type exact ;
- une boucle avec index + `cmp` + accès mémoire est un motif très fréquent de parcours de tableau.
Exercice 1
Un tableau commence à :
Code: Select all
1000h
Quelle est l'adresse de :
Code: Select all
array[5]
Que signifie :
Code: Select all
mov eax, [rbx + rcx*4]
Exercice 3
Quelle taille probable ont les éléments ici ?
Code: Select all
mov rax, [rbx + rcx*8]
Explique la différence entre :
Code: Select all
lea rax, [rbx + rcx*4]
mov eax, [rbx + rcx*4]
Reconstitue le pseudo-C :
Code: Select all
xor ecx, ecx
loop:
mov eax, [rbx + rcx*4]
add eax, 2
mov [rbx + rcx*4], eax
inc rcx
cmp rcx, 5
jl loop
Si un tableau contient 100 éléments de 8 octets, combien d'octets faut-il demander à `malloc` ?
Exercice 7
Que signifie probablement :
Code: Select all
test rax, rax
je failed
Code: Select all
call malloc
Pourquoi :
Code: Select all
[rbx + rcx*8]
Exercice 9
Dans x64dbg, trouve une boucle qui accède à une adresse sous la forme :
Code: Select all
[base + index*scale]
Code: Select all
base
index
scale
nombre d'éléments probable
Explique pourquoi un tableau de structures de 12 octets peut nécessiter un `imul` ou plusieurs additions au lieu d'un simple `*12` directement dans l'opérande mémoire.
