L'ensemble d'instruction ARM
Les microprocesseurs ARM fonctionnent dans deux états principaux (sans compter Jazelle) : ARM et Thumb. Ces états n'ont aucun lien avec les niveaux de privilèges. Par exemple, le code exécuté en mode SVC peut être ARM ou Thumb. La principale différence entre ces deux états réside dans l'ensemble d'instructions : les instructions ARM sont toujours 32 bits, tandis que celles de Thumb sont 16 bits (mais peuvent être 32 bits). Savoir quand et comment utiliser Thumb est particulièrement important pour le développement d'exploits ARM. Lors de l'écriture de shellcode ARM, nous devons supprimer les octets NULL, et l'utilisation d'instructions Thumb 16 bits au lieu d'instructions ARM 32 bits réduit le risque d'en avoir.
Les conventions d'appel des versions ARM sont très confuses et toutes les versions ARM ne prennent pas en charge les mêmes ensembles d'instructions Thumb. ARM a introduit un ensemble d'instructions Thumb amélioré (pseudonyme : Thumbv2) autorisant les instructions Thumb 32 bits et même l'exécution conditionnelle, ce qui n'était pas possible dans les versions précédentes. Afin d'utiliser l'exécution conditionnelle dans l'état Thumb, l'instruction «it» a été introduite. Cependant, cette instruction a été supprimée dans une version ultérieure et remplacée par une instruction censée simplifier les choses, mais ayant eu l'effet inverse. La seule chose que vous devez connaître est la version ARM de votre appareil cible et sa prise en charge spécifique de Thumb afin de pouvoir ajuster votre code.
Version | Introduit avec | Longueur d'instructions | Particularité principale |
---|---|---|---|
Thumb-1 | ARMv4T | 16 bits | Sous-ensemble compact de l'ensemble ARM |
Thumb-2 | ARMv6T2+ | 16 et 32 bits | Ensemble d'instruction étendu, proche d'ARM complet |
ThumbEE | ARMv7-R/E | 16 et 32 bits | Extensions pour environnements gérés |
Comme mentionné précédemment, il existe différentes versions de Thumb. Leur nom différent sert uniquement à les différencier (le processeur lui-même l'appellera toujours Thumb) :
- Thumb-1 (instructions 16 bits) : utilisé dans les architectures ARMv6 et antérieures.
- Thumb-2 (instructions 16 bits et 32 ??bits) : étend Thumb-1 en ajoutant des instructions supplémentaires et en leur permettant d'avoir une largeur de 16 ou 32 bits (ARMv6T2, ARMv7).
- ThumbEE : inclut des modifications et des ajouts destinés au code généré dynamiquement (code compilé sur l'appareil peu avant ou pendant l'exécution).
Différences entre ARM et Thumb :
- Exécution conditionnelle : Toutes les instructions de l'état ARM prennent en charge l'exécution conditionnelle. Certaines versions de processeurs ARM permettent l'exécution conditionnelle dans Thumb grâce à l'instruction IT. L'exécution conditionnelle améliore la densité du code, car elle réduit le nombre d'instructions à exécuter et le nombre d'instructions de branchement coûteuses.
- Instructions ARM et Thumb 32 bits : Les instructions Thumb 32 bits ont un suffixe .w.
- Le décaleur à barillet est une autre fonctionnalité unique du mode ARM. Il permet de réduire plusieurs instructions en une seule. Par exemple, au lieu d'utiliser deux instructions pour une multiplication (multiplier un registre par 2 et utiliser MOV pour stocker le résultat dans un autre registre), vous pouvez inclure la multiplication dans une instruction MOV en utilisant le décalage à gauche de 1 > Mov R1, R0, LSL #1 ; R1 = R0 * 2
Pour changer l'état d'exécution du processeur, l'une des deux conditions suivantes doit être remplie :
- Nous pouvons utiliser l'instruction de branchement BX (branchement et échange) ou BLX (branchement, liaison et échange) et définir le bit de poids faible du registre de destination à 1. Cela peut être réalisé en ajoutant 1 à un décalage, par exemple 0x5530 + 1. On pourrait penser que cela entraînerait des problèmes d'alignement, car les instructions sont alignées sur 2 ou 4 octets. Ce n'est pas un problème, car le processeur ignorera le bit de poids faible.
- Nous savons que nous sommes en mode Thumb si le bit T du registre d'état du programme est défini.
Introduction aux instructions ARM
Cette partie a pour objectif de présenter brièvement l'ensemble d'instructions ARM et son utilisation générale. Il est essentiel de comprendre le fonctionnement des plus petites parties du langage assembleur, leurs interconnexions et les résultats de leur combinaison.
Comme mentionné précédemment, le langage assembleur est composé d'instructions, constituant ses principaux éléments constitutifs. Les instructions ARM sont généralement suivies d'un ou deux opérandes et utilisent généralement le modèle suivant :
MNÉMONIQUE{S}{condition} {Rd}, Operande1, Operande2 |
En raison de la flexibilité de l'ensemble d'instructions ARM, toutes les instructions n'utilisent pas l'intégralité des champs fournis dans le modèle. Néanmoins, l'objectif des champs du modèle est décrit comme suit :
Paramètre | Description |
---|---|
MNÉMONIQUE | Nom court (mnémonique) de l'instruction |
{S} | Suffixe facultatif. Si S est spécifié, les indicateurs de condition sont mis à jour au résultat de l'opération. |
{condition} | Condition devant être remplie pour que l'instruction soit exécutée |
{Rd} | Registre (destination) pour entreposer le résultat de l'instruction |
Operande1 | Premier opérande. Soit un registre, soit une valeur immédiate |
Operande2 | Deuxième opérande (flexible). Peut être une valeur immédiate (nombre) ou un registre avec décalage optionnel. |
Si les champs MNÉMONIQUE, S, Rd et Operande1 sont simples, les champs condition et Operande2 nécessitent quelques précisions. Le champ condition est étroitement lié à la valeur du registre CPSR, ou plus précisément, à la valeur de bits spécifiques du registre. Operande2 est qualifié d'opérande flexible, car il peut être utilisé sous différentes formes : comme valeur immédiate (avec un ensemble limité de valeurs), comme registre ou comme registre avec décalage. Par exemple, nous pouvons utiliser les expressions suivantes comme Operande2 :
- #123 @ Valeur immédiate (avec un ensemble limité de valeurs).
- Rx @ Registre x (comme R1, R2, R3 ...)
- Rx, ASR n @ Registre x avec décalage arithmétique vers la droite de n bits (1 = n = 32)
- Rx, LSL n @ Registre x avec décalage logique vers la gauche de n bits (0 = n = 31)
- Rx, LSR n @ Registre x avec décalage logique vers la droite de n bits (1 = n = 32)
- Rx, ROR n @ Registre x avec rotation à droite de n bits (1 = n = 31)
- Rx, RRX @ Registre x avec rotation à droite d'un bit, avec extension
Pour vous donner un exemple rapide de ce à quoi ressemblent différents types d'instructions, examinons la liste suivante :
- ADD R0, R1, R2 @ Ajoute le contenu de R1 (Opérande1) et R2 (Opérande2 sous forme de registre) et stocke le résultat dans R0 (Rd)
- ADD R0, R1, #2 @ Ajoute le contenu de R1 (Opérande1) et la valeur 2 (Opérande2 sous forme de valeur immédiate) et stocke le résultat dans R0 (Rd)
- MOVLE R0, #5 @ Déplace le numéro 5 (opérande 2, car le compilateur le traite comme MOVLE R0, R0, #5) vers R0 (Rd) UNIQUEMENT si la condition LE (inférieur ou égal) est satisfaite
- MOV R0, R1, LSL #1 @ Déplace le contenu de R1 (opérande 2 sous forme de registre avec décalage logique à gauche) décalé d'un bit vers la gauche vers R0 (Rd). Ainsi, si R1 avait la valeur 2, il est décalé d'un bit vers la gauche et devient 4. 4 est alors déplacé vers R0.
En résumé, examinons les instructions les plus courantes que nous utiliserons dans les exemples futurs :
Instruction | Description | Instruction | Description |
---|---|---|---|
MOV | Déplacer des données | EOR | XOR au niveau du bit |
MVN | Déplacer et négation | LDR | Charger |
ADD | Addition | STR | Entrepose |
SUB | Soustraction | LDM | Charger plusieurs |
MUL | Multiplication | STM | Entreposer plusieurs |
LSL | Décalage logique vers la gauche | PUSH | Pousser sur la pile |
LSR | Décalage logique vers la droite | POP | Dépile le déplacement de pile |
ASR | Décalage arithmétique à droite | B | Branchement |
ROR | Rotation à droite | BL | Branche avec lien |
CMP | Comparer | BX | Branchement avec lien |
AND | ET au niveau du bit | BLX | Branchement avec lien et échange |
ORR | OU au niveau du bit | SWI/SVC | Appel système |