Skip to content
KitploitKITPLOIT
OutilsBlog
Log in
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
page_table_walk — Parcourez les tables de pages x86-64 à la main dans qemu et gdb. Décomposez une adresse virtuelle, suivez cr3 à travers tous les niveaux de la mémoire physique, et extrayez un flag des octets bruts. | Kitploit
Outils/GitHubGitHub/jazho76/page_table_walk
Criminalistique MémoireRétro-ingénierieDébogueursCTFAnalyse de BinairesApprentissage et ÉducationLabs et Pratique
GitHubjazho76/page_table_walk

page_table_walk

Parcourez les tables de pages x86-64 à la main dans qemu et gdb. Décomposez une adresse virtuelle, suivez cr3 à travers tous les niveaux de la mémoire physique, et extrayez un flag des octets bruts.

Voir le dépôt
28112il y a 6 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Capture the Flag dans la mémoire physique

Vous avez lu sur la pagination. Les diagrammes ont du sens. Quatre niveaux, 9 bits chacun, page frame, offset. Bien sûr. Mais ensuite, vous tombez sur un défi qui exige de parcourir réellement les tables de pages, et vous réalisez que vous ne le savez pas. Vous savez à propos de cela. Une grosse différence.

Ce qui a fonctionné pour moi a été de m'asseoir devant QEMU et gdb et de faire le parcours moi-même : calculer chaque index, lire chaque entrée depuis la mémoire physique, suivre chaque pointeur à la main. Un après-midi de cela peut enseigner plus que des heures de cours.

Ceci est une collection de mes notes de ce processus. Si la partie conceptuelle vous manque encore, regardez d'abord la conférence de Zardus sur la gestion de la mémoire du noyau. C'est la théorie. Ceci est le laboratoire.

L'objectif : prendre une adresse virtuelle et la traquer à travers la mémoire physique brute jusqu'à ce que nous trouvions les données. Pas d'aides du noyau. Pas d'abstractions. Juste une VM QEMU, gdb, et de la mémoire physique brute.

À la fin, la pagination ne sera plus quelque chose que vous avez lu, mais quelque chose que vous connaîtrez parce que vous l'avez fait à la main.


Configuration du laboratoire

Un noyau pré-construit et un initramfs sont inclus. J'ai exécuté cela sous Fedora, mais tout système d'exploitation qui exécute QEMU et gdb devrait fonctionner. Installez-les avec votre gestionnaire de paquets :```

Ubuntu/Debian

sudo apt install qemu-system-x86 gdb

Fedora/RHEL

sudo dnf install qemu-system-x86 gdb

macOS

brew install qemu gdb

### Le binaire du défi

La cible est un programme C trivial qui stocke un flag en mémoire et affiche son adresse virtuelle :```c
#include <stdio.h>
#include <unistd.h>

int main(void)
{
    char secret[] = "FLAG{p4g3_t4bl3_w4lk3r}";

    printf("secret @ %p\n", (void *)secret);
    printf("pid = %d\n", getpid());
    printf("Spinning. Walk the page tables to find the flag.\n");

    while (1)
    {
    }
}

La boucle active est intentionnelle. J'utilisais initialement pause(), mais cela met le processus en sommeil dans un appel système : lorsque gdb interrompt la VM, le CPU est probablement en train d'exécuter la tâche idle avec un CR3 différent. Une boucle d'attente active maintient le processus sur le CPU, donc l'arrêt garantit que vous êtes dans son contexte avec les bonnes tables de pages.

Une initramfs pré-construite avec ce binaire est déjà incluse dans initramfs.cpio.gz. Si vous devez la reconstruire (Linux uniquement, nécessite busybox et glibc-static), exécutez make dans ce répertoire.

Lancement de QEMU```

./start.sh

Le script démarre le noyau et l'initramfs fournis sous QEMU avec `-s`
(serveur gdb sur `localhost:1234`) et `nokaslr` afin que les adresses du noyau restent
fixées entre les exécutions.

La VM démarre immédiatement et le binaire du défi s'exécute. Vous verrez l'adresse
virtuelle du flag affichée dans la console.```
secret @ 0x7ffe08985c90
pid = 1
Spinning. Walk the page tables to find the flag.

Écrivez cette adresse virtuelle. C'est votre cible.

Terminal QEMU après le démarrage, montrant la sortie du binaire du défi avec l'adresse du flag et le PID

La touche d'échappement par défaut de QEMU est Ctrl-a, mais cela entre en conflit avec mon préfixe tmux, donc le script utilise -echr 0x11 pour la remapper sur Ctrl-q. Si vous utilisez Ctrl-q pour autre chose, modifiez la valeur hexadécimale dans start.sh pour l'adapter à votre configuration.

Attacher gdb

Dans un second terminal :``` gdb -ex "target remote :1234"

![terminal gdb après attachement, arrêté et prêt](https://assets.kitploit.com/production/public/readmes/12435/9fb13e082596378677ea0d42cf7da3a86706df44127860fc724b2bad0a9f138c.png)

---

## Décomposer l'adresse virtuelle

Vous avez une adresse virtuelle. Mais où se trouvent les données, _vraiment_ ?

Les adresses virtuelles sont la fiction polie du système d'exploitation. Chaque processus pense avoir sa propre mémoire privée commençant à zéro. En réalité, les données se trouvent à un emplacement totalement différent dans la RAM physique. La table des pages est la carte entre les deux : une structure arborescente que le CPU parcourt à chaque accès mémoire (ou qu'il consulte dans son cache TLB).

Faisons donc ce que fait le CPU. Manuellement. Pour traduire cette adresse, nous devons la décomposer en les indices que le CPU utilise à chaque niveau.

Une adresse virtuelle x86-64 a une largeur de 48 bits. Ces 48 bits sont répartis en cinq champs :```
 63    48 47    39 38    30 29    21 20    12 11       0
┌────────┬────────┬────────┬────────┬────────┬──────────┐
│ sign   │  PGD   │  PUD   │  PMD   │   PT   │  Offset  │
│ extend │ index  │ index  │ index  │ index  │          │
│ (16b)  │ (9b)   │ (9b)   │ (9b)   │ (9b)   │  (12b)   │
└────────┴────────┴────────┴────────┴────────┴──────────┘

Chaque index de 9 bits sélectionne une des 512 entrées dans une table des pages à ce niveau. Le décalage de 12 bits sélectionne un octet dans la page finale de 4 Ko (0x1000).

Pour extraire les indices, décaler et masquer :``` PGD index = (VA >> 39) & 0x1FF PUD index = (VA >> 30) & 0x1FF PMD index = (VA >> 21) & 0x1FF PT index = (VA >> 12) & 0x1FF Offset = VA & 0xFFF

Dans gdb, vous pouvez les calculer directement :```
(gdb) p/x (0x7ffe08985c90 >> 39) & 0x1ff
$1 = 0xff
(gdb) p/x (0x7ffe08985c90 >> 30) & 0x1ff
$2 = 0x1f8
(gdb) p/x (0x7ffe08985c90 >> 21) & 0x1ff
$3 = 0x44
(gdb) p/x (0x7ffe08985c90 >> 12) & 0x1ff
$4 = 0x185
(gdb) p/x 0x7ffe08985c90 & 0xfff
$5 = 0xc90

Notez-les. Vous utiliserez chacun à son niveau correspondant.

Vos valeurs seront différentes. L'adresse 0x7ffe08985c90 est juste un exemple. Utilisez l'adresse que votre binaire de défi a affichée.

Note sur la pagination à 5 niveaux. Les processeurs et noyaux récents prennent en charge LA57, ce qui ajoute un cinquième niveau (PML5) au-dessus du PGD et étend les adresses virtuelles sur 57 bits. Le parcours suit le même motif : un indice de 9 bits de plus, une consultation de table de plus. La plupart des systèmes utilisent encore la pagination à 4 niveaux. Vous pouvez vérifier le vôtre : cat /proc/cpuinfo | grep la57. Tout dans cet article suppose une pagination à 4 niveaux.


Trouver CR3 : la racine de l'arbre

Chaque arbre a une racine. Pour les tables de pages, cette racine se trouve dans le registre CR3 : il contient l'adresse physique de la table de plus haut niveau, le PGD. Chaque processus a sa propre valeur CR3, le noyau l'échange lors d'un changement de contexte.

C'est notre point d'entrée dans le parcours. Lisez-la depuis gdb :``` (gdb) info registers cr3 cr3 0x66c7000 [ PDBR=26311 PCID=0 ]

La base de la table de pages est `0x66c7000`. Les 12 bits de poids faible sont l'identifiant PCID/drapeaux (nuls ici),
donc l'adresse de base est la valeur telle quelle.

C'est là que commence la marche.

---

## La marche

Voici l'astuce : chaque niveau
suit le même motif. Les drapeaux varient légèrement entre les niveaux, mais le
processus ne change pas. Le motif :

1. **Calculer l'adresse de l'entrée :** `base + index * 8` (chaque entrée fait 8 octets)
2. **Lire l'entrée depuis la mémoire physique** à l'aide de la commande `xp` du moniteur QEMU
3. **Décoder les drapeaux** (voir la référence ci-dessous). Si Present (bit 0) est à 0, la page n'est pas mappée et la marche s'arrête
4. **Extraire la base de la table suivante :** masquer l'entrée avec `& 0x000FFFFFFFFFF000`
5. **Passer au niveau suivant**
Télécharger l’outil