Skip to content
KitploitKITPLOIT
ИнструментыЭксплойтыБлог
Log in
Отправить
ИнструментыЭксплойтыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
page_table_walk — Вручную исследуйте таблицы страниц x86-64 в qemu и gdb. Разложите виртуальный адрес, проследуйте через cr3 по всем уровням физической памяти и извлеките флаг из сырых байтов. | Kitploit
Инструменты/GitHubGitHub/jazho76/page_table_walk
Криминалистика памятиОбратная инженерияОтладчикиCTFАнализ Бинарных ФайловОбучение и ОбразованиеЛаборатории и Практика
GitHubjazho76/page_table_walk

page_table_walk

Вручную исследуйте таблицы страниц x86-64 в qemu и gdb. Разложите виртуальный адрес, проследуйте через cr3 по всем уровням физической памяти и извлеките флаг из сырых байтов.

Репозиторий
281126 месяцев назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Захват флага в физической памяти

Вы читали о страничной организации памяти. Диаграммы кажутся понятными. Четыре уровня, по 9 бит каждый, страничный кадр, смещение. Конечно. Но затем вы сталкиваетесь с задачей, которая требует реального обхода таблиц страниц, и вы понимаете, что вы не знаете это. Вы знаете об этом. Большая разница.

Что сработало для меня — это сидеть перед QEMU и gdb и выполнять обход самостоятельно: вычислять каждый индекс, читать каждую запись из физической памяти, вручную следовать по каждому указателю. Один такой полдень может научить большему, чем часы лекций.

Это сборник моих заметок из этого процесса. Если вам всё ещё не хватает концептуальной стороны, сначала посмотрите лекцию Зардуса об управлении памятью ядра. Это теория. А это лабораторная работа.

Цель: взять виртуальный адрес и проследить его через сырую физическую память, пока не найдём данные. Никаких вспомогательных средств ядра. Никаких абстракций. Просто виртуальная машина QEMU, gdb и сырая физическая память.

К концу страничная организация памяти не будет чем-то, о чём вы читали, это будет то, что вы знаете, потому что сделали это вручную.


Настройка лаборатории

Предварительно собранные ядро и initramfs включены. Я запускал это в Fedora, но любая ОС, на которой работают QEMU и gdb, подойдёт. Установите их с помощью вашего менеджера пакетов:```

Ubuntu/Debian

sudo apt install qemu-system-x86 gdb

Fedora/RHEL

sudo dnf install qemu-system-x86 gdb

macOS

brew install qemu gdb

### Бинарный файл задачи

Цель — простая программа на C, которая хранит флаг в памяти и выводит его
виртуальный адрес:```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)
    {
    }
}

Цикл ожидания (busy loop) является намеренным. Изначально я использовал pause(), но это переводит процесс в сон через системный вызов: когда gdb останавливает VM, процессор, вероятно, выполняет задачу простоя (idle task) с другим CR3. Цикл ожидания удерживает процесс на процессоре, поэтому остановка гарантирует, что вы находитесь в контексте этого процесса с правильными таблицами страниц.

Предварительно собранный initramfs с этим бинарником уже включён в initramfs.cpio.gz. Если вам нужно пересобрать его (только Linux, требуется busybox и glibc-static), запустите make в этой директории.

Запуск QEMU```

./start.sh

Сценарий загружает прилагаемое ядро и initramfs в QEMU с `-s`
(сервер gdb на `localhost:1234`) и `nokaslr`, чтобы адреса ядра оставались
фиксированными между запусками.

Виртуальная машина загружается немедленно, и запускается исполняемый файл задачи. Вы увидите виртуальный
адрес флага, выведенный в консоль.```
secret @ 0x7ffe08985c90
pid = 1
Spinning. Walk the page tables to find the flag.

Запишите этот виртуальный адрес. Это ваша цель.

Терминал QEMU после загрузки, показывающий вывод бинарного файла задачи с адресом флага и PID

Клавиша выхода QEMU по умолчанию — Ctrl-a, но она конфликтует с моим префиксом tmux, поэтому скрипт использует -echr 0x11 для переназначения на Ctrl-q. Если вы используете Ctrl-q для чего-то другого, измените шестнадцатеричное значение в start.sh в соответствии с вашей настройкой.

Подключение gdb

Во втором терминале:``` gdb -ex "target remote :1234"

![терминал gdb после подключения, остановлен и готов](https://assets.kitploit.com/production/public/readmes/12435/9fb13e082596378677ea0d42cf7da3a86706df44127860fc724b2bad0a9f138c.png)

---

## Разбор виртуального адреса

У вас есть виртуальный адрес. Но где же данные, _на самом деле_?

Виртуальные адреса — это вежливая фикция операционной системы. Каждый процесс думает, что у него есть своя собственная частная память, начинающаяся с нуля. В реальности данные находятся в совершенно несвязанном месте в физической RAM. Таблица страниц — это карта между ними: древовидная структура, которую CPU обходит при каждом обращении к памяти (или ищет в своем кэше TLB).

Итак, давайте сделаем то, что делает CPU. Вручную. Чтобы перевести этот адрес, нам нужно разложить его на индексы, которые CPU использует на каждом уровне.

Виртуальный адрес x86-64 имеет ширину 48 бит. Эти 48 бит разделены на пять полей:```
 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)   │
└────────┴────────┴────────┴────────┴────────┴──────────┘

Каждый 9-битный индекс выбирает одну из 512 записей в таблице страниц на этом уровне. 12-битное смещение выбирает байт в пределах конечной страницы размером 4 КБ (0x1000).

Для извлечения индексов выполните сдвиг и маскирование:``` PGD index = (VA >> 39) & 0x1FF PUD index = (VA >> 30) & 0x1FF PMD index = (VA >> 21) & 0x1FF PT index = (VA >> 12) & 0x1FF Offset = VA & 0xFFF

В gdb вы можете вычислить их напрямую:```
(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

Запишите их. Вы будете использовать каждый на соответствующем уровне.

Ваши значения будут отличаться. Адрес 0x7ffe08985c90 — всего лишь пример. Используйте тот адрес, который напечатала ваша бинарная программа с задачей.

Примечание о 5-уровневой страничной адресации. Современные CPU и ядра поддерживают LA57, что добавляет пятый уровень (PML5) над PGD и расширяет виртуальные адреса до 57 бит. Обход выполняется по тому же шаблону: ещё один 9-битный индекс, ещё один поиск по таблице. Большинство систем всё ещё используют 4-уровневую страничную адресацию. Вы можете проверить свою: cat /proc/cpuinfo | grep la57. Всё в этой статье предполагает 4 уровня.


Поиск CR3: корень дерева

У каждого дерева есть корень. Для таблиц страниц этот корень находится в регистре CR3: он хранит физический адрес таблицы верхнего уровня — PGD. Каждый процесс получает своё собственное значение CR3, ядро меняет его при переключении контекста.

Это наша точка входа в обход. Прочитайте его в gdb:``` (gdb) info registers cr3 cr3 0x66c7000 [ PDBR=26311 PCID=0 ]

База таблицы страниц — `0x66c7000`. Младшие 12 бит — это PCID/флаги (здесь нули), поэтому базовый адрес — это значение как есть.

Отсюда начинается обход.

---

## Обход

Вот хитрость: каждый уровень следует одному и тому же шаблону. Флаги немного различаются между уровнями, но процесс — нет. Шаблон:

1. **Вычисление адреса записи:** `base + index * 8` (каждая запись — 8 байт)
2. **Чтение записи из физической памяти** с помощью команды `xp` монитора QEMU
3. **Декодирование флагов** (см. справочную информацию ниже). Если Present (бит 0) равен 0, страница не отображена, и обход останавливается
4. **Извлечение базы следующей таблицы:** маскирование записи с помощью `& 0x000FFFFFFFFFF000`
5. **Переход на следующий уровень**
Скачать инструмент