Section courante

A propos

Section administrative du site

 Langage  Elément  Tutoriel  Annexe  Aide 
ABAP/4
Ada
Assembleur
Assembly & bytecode
ASP (Active Server Pages)
Basic
C
C++
C# (C Sharp)
Cobol
ColdFusion
Fortran
HTML
Java
JavaScript
LISP
Logo
LotusScript
Oberon
Pascal
Perl
PHP
PL/1
Prolog
Python
Rebol
REXX
Ruby
Rust
SAS
NoSQL
SQL
Swift
X++ (Axapta)
GNAT
SMALLAda
VHDL
Assembleur 370
Assembleur 1802
Assembleur 4004
Assembleur 6502
Assembleur 6800
Assembleur 68000
Assembleur 8080 et 8085
Assembleur 8089
Assembleur 80x86
Assembleur AGC4
Assembleur ARM
Assembleur DPS 8000
Assembleur i860
Assembleur Itanium
Assembleur MIPS
Assembleur PDP-11
Assembleur PowerPC
Assembleur RISC-V
Assembleur SPARC
Assembleur SuperH
Assembleur UNIVAC I
Assembleur VAX
Assembleur Z80
Assembleur Z8000
Assembleur z/Architecture
ASSEMBLER/MONITOR 64
Micol Assembler
GFA Assembler
A86
MASM (Macro Assembler)
TASM (Turbo Assembler)
CIL
Jasmin
LLVM
MSIL
Parrot
P-Code (PCode)
SWEET16
G-Pascal
ASP 1.0
ASP 2.0
ASP 3.0
ASP.NET
ASP.NET Core
ABasiC (Amiga)
Adam SmartBASIC
Altair BASIC
AmigaBASIC (Amiga)
AMOS Basic (Amiga)
Atari Basic (Atari 400, 600 XL, 800, 800XL)
Basic Apple II (Integer BASIC/APPLESOFT)
Basic Commodore 64 (CBM-BASIC)
Basic Commodore 128 (BASIC 7.0)
Basic Commodore VIC-20 (CBM-BASIC 2.0)
Basic Coco 1 (Color Basic)
Basic Coco 2 (Extended Color Basic)
Basic Coco 3 (Extended Color Basic 2.0)
BASICA (PC DOS)
Basic Pro
BBC BASIC
Blitz BASIC (Amiga)
DarkBASIC
Dartmouth BASIC
GFA-Basic (Atari ST/Amiga)
GWBASIC (MS-DOS)
Liberty BASIC
Locomotive BASIC (Amstrad CPC)
MSX-Basic
Omikron Basic (Atari ST)
Oric Extended Basic
Power Basic
Quick Basic/QBasic (MS-DOS)
Sinclair BASIC (ZX80, ZX81, ZX Spectrum)
ST BASIC (Atari ST)
Turbo Basic
Vintage BASIC
VBScript
Visual Basic (VB)
Visual Basic .NET (VB .NET)
Visual Basic pour DOS
Yabasic
BeckerBASIC
SIMONS' BASIC
Basic09 d'OS-9
Disk Extended Color Basic
Basic09 d'OS-9
Disk Extended Color Basic
Access
Excel
Visual Basic pour Windows
Visual Basic .NET pour Windows
C Shell Unix (csh)
C pour Amiga
C pour Atari ST
C pour DOS
C pour Falcon030
C pour GEMDOS (Atari ST)
C pour Linux
C pour PowerTV OS
C pour OS/2
C pour Unix
C pour Windows
Aztec C
CoCo-C
GNU C
HiSoft C
IBM C/2
Introl-C
Lattice C
Microsoft C
MinGW C
MSX-C
Open Watcom C
OS-9 C Compiler
Pure C
Quick C
Turbo C
HiSoft C for Atari ST
HiSoft C for CP/M (Amstrad CPC)
C++ pour OS/2
C++ pour Windows
Borland C++
C++Builder
IBM VisualAge C++
Intel C++
MinGW C++
Open Watcom C++
Symantec C++
Turbo C++
Visual C++
Visual C++ .NET
Watcom C++
Zortech C++
C# (C Sharp) pour Windows
Apple III Cobol
Microsoft Cobol
BlueDragon
Lucee
OpenBD
Railo
Smith Project
Microsoft Fortran
WATFOR-77
CSS
FBML
Open Graph
SVG
XML
XSL/XSLT
LESS
SASS
GCJ (GNU)
JSP
Jython
Visual J++
Node.js
TypeScript
AutoLISP
ACSLogo
LotusScript pour Windows
Amiga Oberon
Oberon .NET
Apple Pascal
Delphi/Kylix/Lazarus
Free Pascal
GNU Pascal
HighSpeed Pascal
IBM Personal Computer Pascal
Lisa Pascal
Maxon Pascal
MPW Pascal
OS-9 Pascal
OSS Personal Pascal
Pascal-86
Pascal du Cray Research
Pascal/VS
Pascal-XT
PURE Pascal
QuickPascal
RemObjets Chrome
Sun Pascal
THINK Pascal
Tiny Pascal (TRS-80)
Turbo Pascal
UCSD Pascal
VAX Pascal
Virtual Pascal
Turbo Pascal for CP/M-80
Turbo Pascal for DOS
Turbo Pascal for Macintosh
Turbo Pascal for Windows
CodeIgniter (Cadre d'application)
Drupal (Projet)
Joomla! (Projet)
Phalanger (PHP .NET)
phpBB (Projet)
Smarty (balise)
Twig (balise)
Symfony (Cadre d'application)
WordPress (Projet)
Zend (Cadre d'application)
PL360
PL/M-80
PL/M-86
Turbo Prolog
CPython
IronPython
Jython
PyPy
AREXX
Regina REXX
JMP
Btrieve
Cassandra
Clipper
CouchDB
dBASE
Hbase
Hypertable
MongoDB
Redis
Access
BigQuery
DB2
H2
Interbase
MySQL
Oracle
PostgreSQL
SAP HANA
SQL Server
Sybase
U-SQL
Introduction
Les codes de conditions
Référence des instructions ARM
Les premiers pas
L'ensemble d'instruction ARM
Chargement/entreposage multiple
Ports d'entrée/sortie cartographié en mémoire dans la TI-Nspire
Préface
Notes légal
Dictionnaire
Recherche

Les premiers pas

Apprendre l'assembleur ARM est une étape cruciale pour quiconque s'intéresse au développement bas niveau ou à la recherche en sécurité informatique, notamment dans le domaine de l'exploitation. Ce langage de programmation permet d'interagir directement avec le matériel via des instructions très proches du processeur, offrant ainsi une maîtrise fine des opérations exécutées. Avant de plonger dans des concepts avancés comme la création de shellcode ou la manipulation de chaînes ROP (Return-Oriented Programming), il est essentiel de comprendre les fondements de l'architecture ARM. Cette série de tutoriels commence donc par poser les bases nécessaires, avec une introduction générale à l'assembleur ARM, suivie d'une exploration progressive des différents composants du langage.

L'environnement de développement est une composante clé pour la pratique de l'assembleur ARM. Même si vous ne possédez pas de matériel physique tel qu'un Raspberry Pi, il est possible de mettre en place un laboratoire virtuel grâce à QEMU, un émulateur de processeur. En y installant une image compatible ARM, comme celle de Raspbian, vous pouvez simuler un système ARM complet sur votre PC. Cela permet d'expérimenter avec du code assembleur dans un environnement sûr, contrôlé et proche du réel. Il est également recommandé de se familiariser avec les outils de débogage comme GDB, permettant d'examiner le contenu des registres, suivre l'exécution instruction par instruction, et comprendre le comportement du code en profondeur.

Au fil des sept parties du tutoriel, plusieurs aspects fondamentaux du langage assembleur ARM seront abordés. On commencera par découvrir les registres et les types de données manipulés, avant de se pencher sur le jeu d'instructions - le cour du langage - permettant de réaliser des opérations arithmétiques, logiques ou de contrôle de flux. Les tutoriels expliqueront aussi comment charger et stocker des données en mémoire, comment effectuer des transferts multiples avec efficacité, et comment gérer la pile et les fonctions. L'exécution conditionnelle et les branchements, très spécifiques à ARM grâce à son architecture optimisée, seront aussi analysés. Cette progression graduelle vise à vous doter de bases solides pour aborder ensuite des scénarios d'exploitation plus complexes dans un contexte ARM.

Pourquoi ARM ?

Ce tutoriel s'adresse généralement à ceux souhaitant apprendre les bases de l'assemblage ARM, et plus particulièrement à ceux s'intéressant à l'écriture d'exploits sur la plateforme ARM. Vous avez peut-être déjà remarqué que les processeurs ARM sont omniprésents. En regardant autour de vous, on compte bien plus d'appareils équipés d'un processeur ARM que de processeurs Intel. Cela inclut les téléphones, les routeurs, sans oublier les objets connectés dont les ventes semblent exploser ces derniers temps. Cela dit, le processeur ARM est devenu l'un des coeurs de processeur les plus répandus au monde. Ce qui nous amène à constater que, comme les PC, les objets connectés sont sujets à des abus de validation d'entrée, tels que les dépassements de tampon. Compte tenu de l'utilisation répandue des appareils ARM et du potentiel d'utilisation abusive, les attaques contre ces appareils sont devenues beaucoup plus fréquentes.

Pourtant, nous avons plus d'experts spécialisés dans la recherche en sécurité x86 que dans ARM, bien que l'assemblage ARM soit peut-être le langage d'assemblage le plus simple et le plus répandu. Alors, pourquoi ne pas se concentrer davantage sur ARM ? Peut-être parce qu'il existe davantage de ressources d'apprentissage sur l'exploitation des vulnérabilités Intel que sur ARM.

Microprocesseur ARM vs microprocesseur Intel

Il existe de nombreuses différences entre Intel et ARM, mais la principale réside dans l'ensemble d'instructions. Intel est un processeur CISC (Complex Instruction Set Computing) doté d'un jeu d'instructions plus vaste et plus riche en fonctionnalités, permettant à de nombreuses instructions complexes d'accéder à la mémoire. Il offre donc davantage d'opérations et de modes d'adressage, mais moins de registres qu'ARM. Les processeurs CISC sont principalement utilisés dans les PC, les stations de travail et les serveurs classiques.

ARM est un processeur RISC (Reduced Instruction Set Computing) et possède donc un ensemble d'instructions simplifié (100 instructions ou moins) et des registres plus polyvalents que CISC. Contrairement à Intel, ARM utilise des instructions qui opèrent uniquement sur les registres et utilise un modèle de chargement/entreposage pour l'accès mémoire, ce qui signifie que seules les instructions de chargement/entreposage peuvent accéder à la mémoire. Cela signifie que l'incrémentation d'une valeur 32 bits à une adresse mémoire particulière sur ARM nécessiterait trois types d'instructions (chargement, incrémentation et d'entreposage) : charger d'abord la valeur à une adresse donnée dans un registre, l'incrémenter dans le registre, puis la stocker en mémoire depuis le registre.

L'ensemble d'instructions réduit présente des avantages et des inconvénients. L'un des avantages est que les instructions peuvent être exécutées plus rapidement, ce qui permet potentiellement une plus grande vitesse (les systèmes RISC raccourcissent le temps d'exécution en réduisant le nombre de cycles d'horloge par instruction). L'inconvénient est que le nombre réduit d'instructions implique une plus grande efficacité d'écriture logicielle avec les instructions limitées disponibles. Il est également important de noter qu'ARM dispose de deux modes : le mode ARM et le mode Thumb. Les instructions Thumb peuvent être de 2 ou 4 octets.

D'autres différences entre ARM et x86 sont :

Il existe des différences non seulement entre Intel et ARM, mais aussi entre les différentes versions d'ARM. Une fois les fondamentaux compris, vous pourrez facilement comprendre les nuances de la version ARM que vous avez choisie. Les exemples de ce tutoriel ont été créés sur un ARMv6 32 bits (Raspberry Pi 1 ; les explications se rapportent donc à cette version.

La dénomination des différentes versions d'ARM peut également prêter à confusion :

Famille ARM Architecture ARM
ARM7 ARM v4
ARM9 ARM v5
ARM11 ARM v6
Cortex-A ARM v7-A
Cortex-R ARM v7-R
Cortex-M ARM v7-M

Écriture en assembleur

Avant de vous lancer dans le développement d'exploits ARM, vous devez d'abord comprendre les bases de la programmation en assembleur, ce qui nécessite quelques connaissances de base avant de pouvoir l'apprécier. Mais pourquoi avons-nous besoin d'assembleur ARM ? N'est-il pas suffisant d'écrire nos exploits dans un langage de programmation/script «normal» ? Ce n'est pas le cas si nous voulons faire de la rétro-ingénierie et comprendre le flux de programmation des binaires ARM, créer son propre shellcode ARM, créer des chaînes ROP ARM et déboguer des applications ARM.

Il n'est pas nécessaire de connaître tous les détails du langage assembleur pour pouvoir faire de la rétro-ingénierie et développer des exploits, mais certains éléments sont nécessaires pour en comprendre le contexte global.

Qu'est-ce que le langage assembleur ? Le langage assembleur n'est qu'une fine couche syntaxique superposée au code machine, composée d'instructions codées en représentations binaires (code machine), ce que notre ordinateur comprend. Alors pourquoi ne pas écrire du code machine ? Ce serait vraiment pénible. C'est pourquoi nous allons écrire de l'assembleur, de l'assembleur ARM, beaucoup plus facile à comprendre pour les humains. Notre ordinateur ne peut pas exécuter de code assembleur lui-même, car il a besoin de code machine. L'un des outils que vous pouvez utilisé pour assembler le code assembleur en code machine est un assembleur GNU du projet GNU Binutils, nommé as, fonctionnant avec les fichiers sources portant l'extension *.s.

Une fois votre fichier assembleur écrit avec l'extension *.s, vous devez l'assembler avec as et le lier avec ld :

as programme.s -o programme.o
ld programme.o -o programme

L'assemblage sous le capot

Commençons par le bas et remontons jusqu'au langage assembleur. Au niveau le plus bas, nous trouvons les signaux électriques de notre circuit. Ces signaux sont formés en commutant la tension électrique sur l'un des deux niveaux suivants : 0 volt (arrêt) ou 5 volts (marche). Comme il est difficile de déterminer la tension du circuit à l'oil nu, nous choisissons d'écrire des motifs de tensions marche/arrêt à l'aide de représentations visuelles, les chiffres 0 et 1, non seulement pour représenter l'absence ou la présence d'un signal, mais aussi parce que 0 et 1 sont des chiffres du système binaire. Nous regroupons ensuite la séquence de 0 et 1 pour former une instruction en code machine, qui est la plus petite unité de travail d'un processeur informatique. Voici un exemple d'instruction en langage machine :

1110 0001 1010 0000 0010 0000 0000 0001

Jusqu'ici, tout va bien, mais nous ne nous souvenons plus de la signification de chacun de ces motifs (de 0 et 1). C'est pourquoi nous utilisons des mnémoniques, des abréviations, pour nous aider à mémoriser ces motifs binaires, où chaque instruction en code machine reçoit un nom. Ces mnémoniques sont souvent composés de trois lettres, mais ce n'est pas obligatoire. Nous pouvons écrire un programme en utilisant ces mnémoniques comme instructions. Ce programme est appelé programme en langage assembleur, et l'ensemble des mnémoniques utilisés pour représenter le code machine d'un ordinateur est appelé le langage assembleur de cet ordinateur. Par conséquent, le langage assembleur est le niveau le plus bas utilisé par les humains pour programmer un ordinateur. Les opérandes d'une instruction viennent après le(s) mnémonique(s). Voici un exemple :

  1. MOV R2, R1

Maintenant que nous savons qu'un programme assembleur est composé d'informations textuelles appelées mnémoniques, nous devons les convertir en code machine. Comme mentionné précédemment, dans le cas de l'assembleur ARM, le projet GNU Binutils fournit un outil appelé as. L'utilisation d'un assembleur comme as pour convertir du langage assembleur (ARM) en code machine (ARM) s'appelle l'assemblage.

En résumé, nous avons appris que les ordinateurs comprennent (réagissent à) la présence ou l'absence de tensions (signaux) et qu'il est possible de représenter plusieurs signaux par une séquence de 0 et de 1 (bits). Nous pouvons utiliser le code machine (séquences de signaux) pour faire réagir l'ordinateur d'une manière bien définie. Comme nous ne nous souvenons pas de la signification de toutes ces séquences, nous leur donnons des abréviations - des mnémoniques - et les utilisons pour représenter des instructions. Cet ensemble de mnémoniques est le langage assembleur de l'ordinateur et nous utilisons un programme appelé Assembleur pour convertir le code de la représentation mnémonique en code machine lisible par ordinateur, de la même manière qu'un compilateur le fait pour les langages de haut niveau.



PARTAGER CETTE PAGE SUR
Dernière mise à jour : Vendredi, le 4 avril 2025