Home Le kit de piratage des serveurs d'Apple
Post
Cancel

Le kit de piratage des serveurs d'Apple

Dans ce nouvel article de blog au titre aguicheur et à l’illustration générée par ChatGPT, on s’intéresse aux serveurs utilisés pour Apple Intelligence, ainsi que son kit dédié à la recherche en sécurité pouvant mener à une prime de 1 million de dollars.

Apple Intelligence

Pendant la présentation d’ouverture de la WWDC 2024, en juin dernier, Apple a présenté son nouveau système d’intelligence personnelle : Apple Intelligence.

En plus d’avoir un nouveau Siri sous stéroïdes, ça permet d’améliorer la qualité des photos, la création d’émojis et d’images personnalisés, ainsi que l’intégration de ChatGPT avec Siri.

L’accent a aussi été mis sur la vie privée et la sécurité de cette nouvelle fonctionnalité. Une partie des requêtes d’Apple Intelligence sont traitées sur l’appareil. Quand ce n’est pas possible, les requêtes sont envoyées vers des serveurs appelés Private Cloud Compute (PCC).

Bug Bounty

Apple dispose de son propre programme de bug bounty. Et pour confirmer leur intérêt pour la sécurité du PCC, le programme a été mis à jour pour supporter ce service cloud.

Le tableau ci-dessous présente les récompenses maximales dans le cadre de son programme de bug bounty pour le PCC.

CategoryDescriptionMaximum Bounty
Remote attack on request dataArbitrary code execution with arbitrary entitlements$1,000,000
Access to a user's request data or sensitive information about the user's requests outside the trust boundary$250,000
Attack on request data from a privileged network positionAccess to a user's request data or other sensitive information about the user outside the trust boundary$150,000
Ability to execute untested code$100,000
Accidental or unexpected data disclosure due to deployment or configuration issue$50,000

Pour encourager les chercheurs à remonter des failles de sécurité, un kit permettant de simuler un nœud PCC virtuel a été mis à disposition.

Ce kit dont on parle dans la suite de ce post, permet aussi de valider de façon indépendante que le PCC respecte bien la confidentialité des utilisateurs comme décrit par Apple.

Private Cloud Compute

Qu-est-ce que le Private Cloud Compute ? Et bien ce sont les serveurs d’Apple utilisés pour traiter les requêtes Apple Intelligence de millions d’appareils Apple.

Dans un article co-écrit par différentes équipes d’Apple on nous présente les PCC nodes comme des serveurs customs basés sur des puces Apple Silicon utilisant le même système de Secure Boot que l’iPhone ainsi qu’une enclave sécurisée.

Le système d’exploitation nommé CloudOS est une version légère et endurcie d’iOS uniquement dédiée aux charges de travail d’inférence des modèles LLM d’Apple.

Firmware

Intéressons nous à la dernière version actuelle de CloudOS : https://updates.cdn-apple.com/private-cloud-compute/a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7.

Le nom de ce fichier correspond à son empreinte SHA256. Il s’agit d’un fichier ZIP, comme tous les firmwares de mise à jour pour les appareils Apple.

1
2
3
4
λ » openssl sha256 a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7 
SHA256(a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7)= a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7
λ » file a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7
a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7: Zip archive data, at least v2.0 to extract

Son contenu est comparable à celui des autres firmwares, incluant plusieurs kernelcaches : un dédié au serveur PCC, un générique pour les Macs équipés de puces M2, ainsi que deux kernelcaches pour des environnements virtuels.

1
2
3
4
λ ~ » ls
092-45721-016.dmg      Firmware                    kernelcache.release.vresearch101   RestoreVersion.plist
092-90931-009.dmg.aea  kernelcache.release.j236c   kernelcache.research.vresearch101  SystemVersion.plist
BuildManifest.plist    kernelcache.release.mac14j  Restore.plist

Le fichier BuildManifest.plist indique que le PCC repose sur une puce M2 Ultra avec comme identifiant ComputeModule14,1.

1
2
3
4
5
6
7
8
9
10
11
12
	<key>Ap,OSReleaseType</key>
	<string>Darwin Cloud</string>
	<key>Ap,ProductMarketingVersion</key>
	<string>18.1</string>
	<key>Ap,ProductType</key>
	<string>ComputeModule14,1</string>
	<key>Ap,SDKPlatform</key>
	<string>iphoneos</string>
	<key>Ap,Target</key>
	<string>J236cAP</string>
	<key>ApChipID</key>
	<string>0x6022</string>

En ce qui concerne le firmware du SEP, trois versions sont présentes dans ce firmware :

  • j236c : PCC, payload non-chiffré.
  • vresearch101 : Environnement virtuel, payload non-chiffré.
  • j475d : Mac Studio avec puce M2 Ultra, payload chiffré.

La différence entre les deux firmwares SEP du PCC (j236c et vresearch101) réside dans le fait que la version virtuelle ne comprend pas le module dxio_swift, qui permet d’interagir avec l’EEPROM du SEP.

Virtual Research Environment

Le VRE pour PCC (PCCVRE) a été annoncé avec l’arrivée de MacOS Sequoia 15.1.

Cette version embarque les outils nécessaires pour faire tourner un PCC virtuel sur un Mac avec une puce Apple Silicon.

test Schema de fonctionnement entre un iPhone et le PCCVRE.

L’outil, pccvre dont nous avons besoin est situé dans /System/Library/SecurityResearch/usr/bin. Ce programme permet de créer et d’interagir avec les instances virtuelles de PCC. Assurez-vous d’activer le mode research-guest.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
λ /System/Library/SecurityResearch/usr/bin » ./pccvre 
OVERVIEW: Private Cloud Compute Virtual Research Environment tool.

USAGE: pccvre <subcommand>

OPTIONS:
  -h, --help              Show help information.

SUBCOMMANDS:
  license                 Trigger the license agreement acceptance.
  release                 Interact with releases in the Private Cloud Compute Transparency Log.
  instance                Manage Virtual Research Environment instances.
  image4                  Extract and package image4 objects.
  cryptex                 Build cryptexes for usage with the VRE.
  attestation             Operations over PCC Attestation Bundles.

  See 'pccvre help <subcommand>' for detailed help.

Avant de créer une première instance, il nous faut lister puis télécharger une version de CloudOS.

1
2
3
4
5
6
λ ~ » pccvre release list
Found 10 releases:

    2764: f7d2d2f61fb08ad0d9894910efb2c25c45fa0279ddb40ae4d040bb73f0a62486
    2751: eda75e9dd776cf0182799f6aac3168529f4348be8bbe4cc608a18eeec0bfdd1b
...

Pour cet exemple, je choisis la dernière version à ce jour.

1
2
3
4
5
6
7
8
9
10
11
12
λ ~ » pccvre release download --release 2764
Downloading asset: https://updates.cdn-apple.com/private-cloud-compute/a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7
SW release asset downloaded: a1245d6d56b3de6009d1bbfe452262ff2e953034636e9b47d83e4c2a2d2540f7
Downloading asset: https://updates.cdn-apple.com/private-cloud-compute/f2117c0de766830f4887c6370f58ca234a13a193a8fe0a064bdd1577b9c6269c
SW release asset downloaded: f2117c0de766830f4887c6370f58ca234a13a193a8fe0a064bdd1577b9c6269c
Downloading asset: https://updates.cdn-apple.com/private-cloud-compute/f81cafe498a1081049be16f3c7bc58468d3cb7722aebe365632d99fc0f8a389a
SW release asset downloaded: f81cafe498a1081049be16f3c7bc58468d3cb7722aebe365632d99fc0f8a389a
Downloading asset: https://updates.cdn-apple.com/private-cloud-compute/b5a2a77df4ecd3771c9002e46e065336b7414ba45c7962eea05cb15ee6f0f90c
SW release asset downloaded: b5a2a77df4ecd3771c9002e46e065336b7414ba45c7962eea05cb15ee6f0f90c
Downloading asset: https://updates.cdn-apple.com/private-cloud-compute/74301a8be3c61debc1377a80b7eeebfbab36639176a5192768ebfe4ccc368a37
SW release asset downloaded: 74301a8be3c61debc1377a80b7eeebfbab36639176a5192768ebfe4ccc368a37
Completed download of assets.

Créons une première VM avec cette même version : pccvre instance create -N pcc -R 2764.

Cette commande créée une VM, puis la restaure. Elle apparaît ensuite dans la liste des instances avec le statut éteint.

1
2
3
λ ~ » pccvre instance list                 
name                 status     ecid              ipaddr
pcc                  shutdown   1b93e8666b60d56a  -

Faisons un premier démarrage de la VM : pccvre instance start -N pcc. pcc_instance_boot Premier démarrage d’une instance pccvre.

Les premières lignes de logs nous indiquent qu’un serveur HTTP ainsi qu’un serveur GDB sont disponibles.

La suite des logs correspond au lancement du bootloader, mais il n’y a aucun log pour le reste de la séquence de démarrage.

Donc pour l’instant il n’y a pas beaucoup d’interaction avec cette instance. La première étape serait de tenter une connexion au serveur HTTP en écoute. Mais il semble que cela fasse planter le processus (oups 😬).

pcc_instance_boot Crash de la commande pccvre.

Lors du crash de la commande pccvre, la VM est encore en cours d’exécution via le processus vrevm (qui est l’actuel programme qui gère les VMs). Mais il n’est plus possible d’interagir avec celle-ci.

Il faudra donc tuer ce processus pour pouvoir à nouveau démarrer la VM.

Verbose boot

Rendons ce démarrage un peu plus verbeux. L’iBoot de type RELEASE utilisé en production ne prend pas en compte les arguments de démarrage, il faut pour cela utiliser la variante RESEARCH_RELEASE.

Créons une nouvelle VM en mode “research”.

1
pccvre instance create -N pcc-research -R 2764 --variant research

Vous remarquerez que les logs de restauration de la VM sont déjà plus verbeux. Néanmoins, le démarrage de la VM post-restauration ne l’est pas.

Entre en jeu un nouvel outil : vrevm. Cet outil se trouve dans le même répertoire que pccvre. Et pour gérer les VMs il est beaucoup plus complet.

Ajoutons le boot-arg suivant avec vrevm après avoir stoppé la VM :

1
vrevm modify -N pcc-research -B serial=3

Lors du prochain démarrage les logs de démarrage du noyau et du reste de l’OS s’afficheront pcc_instance_boot PCCVRE verbose boot.

Une autre option pour configurer ce boot-arg est de l’ajouter aux variables NVRAM.

1
vrevm modify -N pcc-research --nvram boot-args="serial=3"

SSH

Il y a la possibilité de se connecter en SSH aux instances. Il suffit de configurer la VM pour démarrer un serveur SSH.

Il est nécessaire que vous ayez au préalable généré une paire de clé SSH, à spécifier avec la commande suivante :

1
2
λ ~ » pccvre instance configure ssh -N pcc-research -p ~/.ssh/id_ed25519.pub
Enabled SSH service for VRE instance.

Cette commande permet de monter un cryptex, (une image disque contenant un trustcache pour les binaires), et d’ajouter un démon SSH ainsi que quelques utilitaires comme bash, zsh et d’autres outils similaires aux GNU Core Utils.

Démarrez la VM, puis dans un nouveau shell, récupérez son IP et connectez-vous en SSH en tant que root.

1
2
3
4
5
6
7
8
9
λ ~ » pccvre instance list
name                 status     ecid              ipaddr
pcc-research         running    d3b4c0445d8c690d  192.168.64.17
λ ~ » ssh root@192.168.64.17
...
localhost# whoami 
root
localhost# uname -a
Darwin localhost 24.1.0 Darwin Kernel Version 24.1.0: Fri Oct 11 09:18:06 PDT 2024; root:xnu-11215.40.59~75/RELEASE_ARM64_VRESEARCH1 ComputeModule14,2 

Restricted Execution Mode (REM)

Certains binaires comme /bin/ps sont restreints et ne peuvent pas être lancés après la séquence d’initialisation du système.

1
2
localhost# ps
zsh: killed     ps

D’après les logs de la VM, c’est à cause du Restricted Execution Mode.

1
2
AMFI: When validating /bin/ps:
  com.apple.ps not allowed for Restricted Execution Mode state (enabled)

Pour changer le mode de REM, il faut extraire, éditer, et réimporter la config de darwin-init:

1
2
3
pccvre instance configure darwin-init dump -N pcc-research > /tmp/darwin-init.json
sed -i '' 's/rem/rem-dev/g' /tmp/darwin-init.json
pccvre instance configure darwin-init set -I /tmp/darwin-init.json -N pcc-research

Au prochain démarrage la commande ps sera executable.

1
2
3
4
localhost# ps   
  PID TTY           TIME CMD
  229 ttys000    0:00.00 -zsh
  236 ttys000    0:00.00 ps

Cryptex

On a précédemment chargé un cryptex qui contient un serveur SSH. Mais comment peut-on créer notre propre cryptex qui embarque nos binaires ? En utilisant l’infrastructure de build du SRD.

Les cryptex ont initialement été créés pour permettre aux chercheurs en sécurité de charger leurs propres outils en ligne de commande sur cet iPhone.

Apple a fourni une doc pour ces chercheurs ainsi qu’une suite d’exemples pour avoir un accès SSH sur ces iPhones. On peut donc se baser dessus pour y charger nos propres outils.

Pour cela, nous pouvons utiliser le dépôt Git de Jon Palmisc. Par exemple, son code démarre un serveur à l’écoute sur le port 7070 et retourne le PID ainsi que le point de montage du cryptex.

Compilez le code avec make et créez le cryptex : pccvre cryptex create --source ./build/com.example.barebones.root/ -V research.

Il ne reste qu’à configurer la VM pour utiliser ce cryptex : pccvre instance configure cryptex add -N pcc-research -C research:research.aar.

Une fois la VM démarrée, un nouveau processus est lancé. Il est alors possible d’établir une connexion avec la VM sur le port 7070, qui renvoie le PID du serveur ainsi que le point de montage du cryptex.

1
2
λ ~ » nc -c 192.168.64.17 7070
pid=105, CRYPTEX_MOUNT_PATH="/private/var/PrivateCloudSupportInternalAdditions"

Jon fourni aussi un autre dépôt permettant d’utiliser Toybox, un clone de Busybox, apportant plus d’outils que le cryptex SSH initial fourni par Apple.

Après avoir cloné le dépôt, supprimez les répertoires superflus et compilez le code.

1
2
rm -rf apps/{debugserver,dropbear,run,user}
make

Créez un nouveau cryptex : pccvre cryptex create --source ./com.jonpalmisc.srdsh.root -V research.

Configurez le cryptex sur la VM : pccvre instance configure cryptex add -N pcc-research -C research:research.aar.

Une fois la VM démarrée, darwin-init se charge de monter le nouveau cryptex en tant que volume. Rajoutez le chemin du répertoire contenant toybox au $PATH pour utiliser tous les outils.

pcc_instance_boot PCCVRE avec toybox installé.

Avec ces outils il est possible d’installer vos propres logiciels sur le PCC avec une bonne infrastructure d’automatisation.

Kernel Debugging

Au démarrage, pccvre nous indique qu’un stub GDB est disponible. C’est à dire que via GDB, on peut s’y connecter pour y debug un programme.

1
2
3
4
λ ~ » pccvre instance start -N pcc-research
HTTP service started: 192.168.64.1:54613
Starting VM: pcc-research (ecid: 235f877841f331ff)
GDB stub available at localhost:54614

D’après la documentation ce stub permet de faire du kernel debugging et donc de pouvoir lire et écrire dans la mémoire du noyau.

1
2
3
4
5
6
7
8
9
10
11
12
13
λ ~ » lldb
(lldb) gdb-remote localhost:54614
Kernel UUID: 253A531F-EAF2-3468-9105-6BFD27734BA9
Load Address: 0xfffffe00400bc000
WARNING: Unable to locate kernel binary on the debugger system.
Process 1 stopped
* thread #1, name = 'CPU1', stop reason = signal SIGSTOP
    frame #0: 0xfffffe0040bbf8a8
->  0xfffffe0040bbf8a8: mov    w0, #0x1 ; =1 
    0xfffffe0040bbf8ac: bl     0xfffffe0040bcf6e4
    0xfffffe0040bbf8b0: mov    w0, #0x0 ; =0 
    0xfffffe0040bbf8b4: bl     0xfffffe0040bbf964
Target 0: (No executable module.) stopped.

Le problème c’est qu’on a pas les symboles pour pouvoir debug efficacement alors que c’est pourtant l’objectif de cette fonctionnalité.

iBoot

Tout comme les iPhones, le mode recovery est accessible depuis le PCCVRE.

Pour que la VM démarre en mode recovery il y a deux méthodes : dans la console de la VM, pendant le démarrage, appuyez plusieurs fois et rapidement sur la touche entrée de votre clavier jusqu’à avoir un shell.

Ou modifiez le paramètre de NVRAM auto-boot : vrevm modify -N pcc-research --nvram auto-boot=false.

Lorsque la VM démarre, la séquence de démarrage s’arrête au niveau de l’iBoot qui propose un prompt.

pcc_instance_boot PCCVRE en mode recovery.

Ce shell n’est pas d’une grande utilité puisqu’il retourne une erreur de syntaxe sur la plupart de commandes.

1
2
3
4
5
Entering recovery mode, starting command prompt
] help
help
?SYNTAX ERROR
] 

Tout comme un iPhone, l’appareil est détecté en tant qu’appareil en mode de récupération.

1
2
3
4
5
6
7
8
9
10
λ ~ » system_profiler SPUSBDataType
USB:
    AppleUSBUserHCI:
      Host Controller Driver: AppleUSBUserHCI
        Apple Mobile Device (Recovery Mode):
          Product ID: 0x1281
          Vendor ID: 0x05ac (Apple Inc.)
          Version: 0.00
          Serial Number: SDOM:01 CPID:FE01 CPRV:00 CPFM:03 SCEP:01 BDID:90 ECID:037192D694AC96D3 IBFL:3D SRNM:[Z6WDC44R0T]

Ça nous permet d’utiliser des outils irecovery pour communiquer en USB avec la VM en mode recovery.

Debugging

En mode recovery, le stub GDB nous permet de debug l’iBoot. Dans un précédent article j’ai expliqué comment faire la même chose mais avec GDB. Cette fois-ci on va utiliser IDA.

Avant de charger le fichier dans IDA, je vous conseille d’installer un loader de qualité pour simplifier l’analyse du binaire : ida-iboot-loader.

Ensuite configurez le debugger d’IDA pour se connecter au port du stub dans Debugger -> Process Options. pcc_instance_boot IDA debugger options.

Pour valider que le debugger fonctionne bien rajoutons un breakpoint au niveau de l’instruction qui affiche la chaîne de caractère SYNTAX ERROR, à l’adresse 0x700C4A70.

Cliquez sur le bouton “play”, la session de debugging commence. Si on tape n’importe quelle commande dans le prompt de l’iBoot, le breakpoint est déclenché et stoppe l’exécution de l’iBoot à notre adresse cible.

pcc_instance_boot IDA iBoot debugging.

Secure Enclave Processor

L’architecture du PCC dépend fortement du SEP, que ça soit pour l’intégrité du démarrage mais aussi pour le processus de validation de signature de code.

Contrairement au SRD, il semble que le SEP, rentre dans le scope du bounty d’Apple pour le PCC.

Cela nous permet d’avoir pour la première fois (et de façon volontaire de leur part cette fois) un sep-firmware non-chiffré.

Debugging

Il n’y a pas d’indication sur la possibilité du debugging le SEP. Mais jetant un œil au code source, il y a bien un stub qui est créé pour ce coprocesseur.

1
2
3
4
5
6
7
8
let sep_config = _VZSEPCoprocessorConfiguration(storageURL: bundle.sepStoragePath)
if let avpsepbooter { // default AVPSEPBooter.vresearch1.bin from VZ framework
	sep_config.romBinaryURL = avpsepbooter
}
sep_config.debugStub = _VZGDBDebugStubConfiguration()
config._coprocessors = [sep_config]

pconf._isProductionModeEnabled = (platformFusing == .prod)

En listant les ports ouverts sur ma machine il y en a deux qui se suivent. L’un est le stub pour l’AP, affiché dans les logs, mais pas d’info sur le second.

1
2
3
4
5
6
λ ~ » lsof -PiTCP -sTCP:LISTEN
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
pccvre    736 mathieu   14u  IPv4 0xd9d0fec252f367b1      0t0  TCP 192.168.64.1:49171 (LISTEN)
com.apple 744 mathieu    7u  IPv4 0xc90a6442200cbe76      0t0  TCP localhost:49172 (LISTEN)
com.apple 744 mathieu    9u  IPv4 0x67dc01d0bbc249e5      0t0  TCP localhost:49173 (LISTEN)
com.apple 746 mathieu    3u  IPv4 0x67dc01d0bbc249e5      0t0  TCP localhost:49173 (LISTEN)

La connexion via lldb confirme que cela correspond à un serveur GDB

1
2
3
4
5
6
7
8
9
10
λ ~ » lldb
(lldb) gdb-remote localhost:49173
Process 1 stopped
* thread #1, name = 'CPU1', stop reason = signal SIGSTOP
    frame #0: 0xffffffff0000ae58
->  0xffffffff0000ae58: bl     0xffffffff000056b4
    0xffffffff0000ae5c: ldr    x8, [x20, #0xc48]
    0xffffffff0000ae60: cbz    x8, 0xffffffff0000ae50
    0xffffffff0000ae64: mrs    x8, CNTPCT_EL0
Target 0: (No executable module.) stopped.

Si on désassemble du code plus haut dans l’espace d’adressage, on tombe sur ces instructions ci-dessous.

Si elles vous semblent familières c’est que vous faites partie du cercle très fermé des gens qui analysent le SEP 😎.

1
2
3
4
5
(lldb) dis -s 0xffffffff00000000
    0xffffffff00000000: adr    x1, 0xffffffff00000000
    0xffffffff00000004: adr    x2, 0xffffffff00000800
    0xffffffff00000008: msr    VBAR_EL1, x2
    0xffffffff0000000c: b      0xffffffff00004000

Ces instructions correspondent au code de démarrage du firmware du SEP qui jump ensuite à l’adresse qui contient le kernel.

Pour être sûr de ce que j’avance, on peut désassembler le code du binaire qui correspond au kernel du SEP et faire la même chose avec LLDB.

On voit bien que le code est similaire.

pcc_instance_boot Comparaison du code désassemblé dans IDA et LLDB.

Via LLDB si on change le PC vers une adresse qui n’existe pas, cela fait bien paniquer le kernel du SEP d’après les logs de la VM, confirmant qu’il est bien possible de le debugger.

pcc_instance_boot Crash du kernel de SEPOS (elfour aka L4) avec LLDB.

Si vous démarrez le PCC en mode recovery, vous aurez la possibilité de debug la SEPROM virtuelle utilisée à la place du sep-firmware, car il n’est pas encore chargé.


Pour conclure, il y a encore pas mal de choses à faire et à découvrir autour du PCC, mais qui ne sont documentées nulle part. Par exemple charger une nouvelle version de SPTM et TXM.

Ou compiler sa propre version de pccvre, car même si le code est open source, il dépend de bibliothèques de fonctions qui ne sont pas publiques.

L’option pour charger sa propre version d’iBoot n’est pas activée pour l’instant et pas de possibilité de charger une version modifiée du sep-firmware.

Pour ce qui est du fuzzing de certains composants ça peut être une meilleure alternative que Corellium (surtout niveau pricing pour le SEP et l’iBoot).

En parlant de Corellium, on pourra peut-être un jour avoir la possibilité de virtualiser tout un iPhone avec un outil similaire à pccvre (du genre srdvre).


Initialement ce blog post, c’était juste une suite de postes sur Mastodon , mais ça semblait intéressant à partager plus globalement sur Twitter. Et ça m’a permis d’attirer l’attention des gens qui travaillent sur ce projet.

Un autre autre article devrait sortir bientôt, par quelqu’un d’autre, cette fois-ci en anglais et qui traite le sujet plus en détails. Donc n’hésitez pas à revenir d’ici quelques jours, le lien sera tout en haut de l’article.

Liens et sources :

This post is licensed under CC BY 4.0 by the author.