Отладчики вглубь: gdb, x64dbg, точки останова

Статический анализ показывает, что программа может делать; отладчик показывает, что она делает на самом деле — прямо сейчас, с конкретными данными.

Отладчик (debugger) — инструмент, который запускает программу под своим контролем: умеет приостанавливать её в выбранных точках, показывать и менять регистры, память и стек, выполнять код по одной инструкции. Для аналитика это «замедленное кино» работы бинарника.

Чтение дизассемблера — это разбор статичного текста: вы видите инструкции, но не видите, какие значения по ним проходят. Отладчик снимает это ограничение. Он позволяет остановить выполнение ровно там, где интересно, и заглянуть внутрь: что лежит в регистрах, что на стеке, по какой ветке пошёл код. Для исследователя вредоносного ПО или участника CTF это основной способ понять логику, которую невозможно вычислить «на бумаге» — например, значение, расшифрованное только в рантайме.

Зачем это знать аналитику и защитнику

Многое в современном ПО раскрывается лишь во время работы: ключи расшифровываются на лету, конфигурация собирается из кусков, проверки зависят от окружения. Статически такой код выглядит как «шум». Отладчик превращает шум в наблюдаемые факты: вы ставите точку останова после расшифровки и просто читаете готовую строку из памяти. Для защитника это рабочий инструмент анализа подозрительного образца в изолированной лаборатории: понять, что делает семпл, какие данные он формирует, по каким условиям ветвится.

Два инструмента: gdb и x64dbg

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

Действиеgdb (команда)x64dbg
Точка останова по адресу/функцииbreak *0x401000 / break mainF2 на строке дизассемблера
Запуск / продолжитьrun / continueF9
Шаг с заходом в вызовstepiF7
Шаг без захода (через call)nextiF8
Показать регистрыinfo registersокно Registers

Точки останова (breakpoints)

Точка останова — пометка «останови выполнение, когда дойдёшь сюда». Это базовый приём: вы помечаете интересный адрес (начало функции проверки, место после расшифровки строки), запускаете программу, и она замирает на этом месте, отдавая вам управление. Технически программный брейкпоинт реализуется подменой одного байта инструкции на специальный код-ловушку, по которому процессор передаёт управление отладчику; перед продолжением отладчик прозрачно возвращает оригинальный байт. Знать этот механизм важно — на нём же строятся некоторые приёмы анти-отладки из третьего урока.

(gdb) break main          # остановиться на входе в main
(gdb) run                 # запустить программу
(gdb) info registers rax  # прочитать один регистр
(gdb) x/8xb $rsp          # 8 байт памяти со стека в hex

Вотчпоинты (watchpoints)

Иногда вопрос не «где выполнение», а «кто изменил это значение». Тут помогает вотчпоинт — точка останова не на коде, а на данных: программа замирает, как только указанная ячейка памяти или переменная меняется. Это незаменимо, когда надо найти, в каком месте кода присваивается флаг или портится буфер.

(gdb) watch counter       # стоп при изменении переменной counter
(gdb) rwatch secret       # стоп при чтении secret
(gdb) continue            # бежим, пока значение не тронут

Под капотом вотчпоинты обычно используют отладочные регистры процессора (hardware breakpoints): CPU сам следит за обращением к адресу, поэтому такой брейк не замедляет программу и не меняет её байты.

Регистры и стек в рантайме

Главная ценность отладчика — возможность читать состояние процессора в любой момент паузы. Регистры (rax, rbx, rsp, rip и др.) хранят аргументы, промежуточные значения и адрес текущей инструкции; стек хранит локальные переменные и адреса возврата. Остановившись в нужной точке, вы буквально видите, что программа держит «в руках».

Чтобы прочувствовать, как из «сырых» байтов получается осмысленное значение, разберём, как читается little-endian-число — именно так регистры и память показываются в отладчике:

# Байты в памяти (little-endian) -> целое число, как делает отладчик
raw = bytes([0x2a, 0x00, 0x00, 0x00])   # как лежит в памяти 4-байтное число
value = int.from_bytes(raw, "little")
print("hex в памяти:", raw.hex())
print("значение:", value)

Вывод:

hex в памяти: 2a000000
значение: 42

Понимание порядка байтов критично: в дампе памяти число 42 выглядит как 2a 00 00 00, и без этого знания легко неверно прочитать значение из стека или регистра.

Как это работает под капотом

Отладчик и отлаживаемая программа — два процесса, связанных механизмом ОС. На Linux это системный вызов ptrace: он позволяет одному процессу читать и менять регистры и память другого, ставить точки останова и получать управление при остановках. На Windows аналогичную роль играет Debugging API (события отладки, DebugActiveProcess, доступ к памяти через ReadProcessMemory). Важный вывод: отлаживаемая программа в принципе может заметить, что находится под наблюдением, — потому что присоединение отладчика оставляет следы в состоянии процесса. Именно это используют техники анти-отладки.

Как защититься

В контексте reverse-engineering «защита» имеет два смысла. Со стороны аналитика важна безопасность лаборатории: запускайте незнакомый бинарник только в изолированной ВМ без доступа к ценным данным и сети, делайте снимок (snapshot) до запуска, чтобы откатиться. Со стороны разработчика, который не хочет лёгкого анализа своего ПО, помогают (с оговоркой, что от мотивированного исследователя они лишь поднимают планку): сборка без отладочных символов, обфускация, контроль целостности кода. Но опираться на «секретность через сложность» как на единственную защиту нельзя — настоящие секреты (ключи, пароли) не должны быть зашиты в клиентский бинарник вообще.

Юридическое напоминание: отлаживать и анализировать допустимо своё ПО, программы со свободной лицензией, учебные crackme и образцы в рамках легального исследования. Обход технических средств защиты и анализ чужого ПО в нарушение лицензии могут быть незаконны (см. УК РФ ст. 272/273 — неправомерный доступ и вредоносное ПО). Работайте на своих файлах и в разрешённых лабораториях.

Итоги

  • Отладчик запускает программу под контролем: пауза в нужной точке, чтение и изменение регистров, памяти и стека, пошаговое выполнение.
  • Точки останова ловят момент по адресу/функции; вотчпоинты — по обращению к данным (часто через аппаратные отладочные регистры).
  • gdb (Linux, консоль) и x64dbg (Windows, GUI) различаются интерфейсом, но реализуют одни и те же приёмы.
  • Под капотом — ptrace на Linux и Debugging API на Windows; присоединение отладчика оставляет следы, что и эксплуатирует анти-отладка.
  • Безопасность аналитика — изолированная ВМ и снапшоты; настоящие секреты не зашивают в клиентский бинарник.
Проверьте себя
1. Чем точка останова (breakpoint) отличается от вотчпоинта (watchpoint)?
ABreakpoint останавливает по достижении адреса/функции в коде, а watchpoint — при чтении или изменении ячейки памяти/переменной
BBreakpoint работает только в gdb, а watchpoint — только в x64dbg
CBreakpoint меняет значение регистра, а watchpoint его читает
DМежду ними нет разницы, это синонимы
2. Как 4-байтное число 42 (0x2A) лежит в памяти в формате little-endian?
A00 00 00 2a
B2a 00 00 00
C42 00 00 00
D00 2a 00 00