Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Сегодня мы разберемся, как привычная отладочная информация может обернуться неожиданной головной болью при реверсе. Возьмем бинарь Mach-O под macOS ARM64: с виду — символы есть, а по факту — сплошной мусор. Разберем, почему так происходит, как вручную найти настоящую таблицу символов и строк, и напишем простой скрипт, который вернет читаемые имена обратно в твою IDA.
Мы очень быстро привыкаем к полезным мелочам, делающим нашу жизнь удобнее. Настолько привыкаем, что не замечаем их, когда они есть, однако очень болезненно воспринимаем момент, когда они по каким‑то причинам внезапно пропадают. В этом случае приходится заново учиться базовым вещам, горестно вспоминая, как же мы жили без полезных инструментов раньше.
Сегодня мы поговорим об отладочных символах (я думаю, ты уже достаточно глубоко погружен в темы кодинга и реверса, чтобы не нужно было на пальцах объяснять, что это такое, для чего и как используется). Во время отладки программы к ним так привыкаешь, что забываешь убрать их во время финальной компиляции. С другой стороны, хакеры настолько приспособились к тому, что программист забыл убрать отладочную информацию из программы, что воспринимают ее наличие как нечто само собой разумеющееся и временами теряются, если ее не оказывается на месте или с ней что‑то не так.
Давай попробуем разобраться с конкретным случаем, когда отладочная информация в реверсируемом файле вроде как присутствует, но воспользоваться ей напрямую не получается. Для примера мы возьмем некое приложение под macOS формата Mach-O для архитектуры ARM64.
Мы когда‑то начинали разбирать этот формат в статье «Липосакция для fat binary. Ломаем программу для macOS с поддержкой нескольких архитектур», поэтому сегодня будем сочетать полезное с полезным и продолжим разбор, делая упор уже на символьной информации, содержащейся в таких файлах. Загружаем нашу программу в IDA.
На первый взгляд, отладочная информация в файле вроде как присутствует, большинство функций имеют имена. Однако при ближайшем рассмотрении эти «имена» оказываются бессмысленным набором символов без конца и начала и совершенно не вписываются в логику программы.
Можно было бы предположить злонамеренную обфускацию, о которой я говорил в нескольких предыдущих статьях, однако для обфускации имена, наоборот, чересчур осмысленны. Вдобавок по логике проще выкинуть из модуля всю отладочную информацию начисто, чем заморачиваться с ее обфускацией, преследуя непонятную цель.
Соглашусь: последний аргумент несколько слабоват, поскольку за время нашего знакомства нам неоднократно попадались творения с абсолютно нечеловеческой логикой, однако априори я все‑таки стараюсь придерживаться хорошего мнения о людях. Попробуем проанализировать отладочную информацию этого модуля другими средствами. В конце концов, на IDA свет клином не сошелся, функция анализа Debug Symbols Mach-O встроена, например, в тот же Detect It Easy.
Для ее просмотра необходимо нажать слева кнопку «Информация о файле» или кнопку «Mach-O». В открывшемся окне нас интересует расположенная слева вкладка «Команды → LC_SYMTAB → Таблица символов».
Даже на первый взгляд (а мы вернемся к подробному разбору этой таблицы позднее) там содержится полная каша. Однако, пролистав таблицу чуть ниже, мы натыкаемся на более осмысленную информацию, что дает нам надежду на успешное излечение пациента.
Строки в правой колонке напоминают неправильно порезанные декорированные имена символов (если ты еще не слышал о таких, вот ссылка, а мы пока не будем отвлекаться на эту тему). Если мы ткнемся во вкладку «LC_SYMTAB → Таблица строк → Строки», то получим и вовсе полностью валидные декорированные имена функций.
То есть отладочные имена функций в файле все‑таки присутствуют. Попробуем разобраться, что в них не так, почему ни Detect It Easy, ни IDA их правильно не воспринимают, после чего попытаемся руками самостоятельно починить их.
Для этого нам придется снова окунуться в матчасть.
Вспомним статью, в которой мы начинали разбирать формат Mach-O. Правда, там шла речь о «жирном» бинарнике, в котором содержался код, предназначенный для нескольких процессоров, наш же случай проще: у нас бинарник «обезжиренный» и в нем содержится только код для процессора ARM64. В двух словах передам суть полезной информации из той статьи для тех, кому лень читать.
Типичный файл Mach-O состоит из трех областей. Заголовок содержит общую информацию о двоичном файле: порядок байтов (магическое число), тип процессора, количество команд загрузки и так далее. Команды загрузки — это своего рода оглавление, которое описывает положение сегментов, таблицу символов, динамическую таблицу символов и тому подобное. Каждая команда загрузки содержит метаданные, такие как тип команды, ее имя, позиция в двоичном файле и прочие полезные сведения.
Третья область — данные — обычно самая большая часть объектного файла. Она содержит код и данные, такие как таблицы символов, которые, собственно, нас и интересуют. Не будем подробно останавливаться на структурах формата Mach-O, ты можешь самостоятельно изучить их в качестве домашнего задания, прочитав вот эту статью или разбирая код и описание к нему этого парсера с GitHub. Поскольку мы изначально выбрали Detect It Easy в качестве инструмента для анализа, для экономии времени поручим парсинг именно ему. Начнем с заголовка файла.
Как я уже говорил, наш бинарник «постный», поэтому он сразу начинается с mach_header
. Помимо типа процессора, в заголовке мы видим, что за ним следуют 0x2d
команды загрузки общим размером 0x16F0
байт. Каждая команда имеет собственную структуру и собственный размер, поэтому, полагаясь на DIE, сразу находим интересующую нас команду LC_SYMTAB
, содержащую ссылки на отладочные символы (я упоминал ее в начале статьи).
Как следует из спецификации, формат у этой команды следующий:
struct symtab_command { // LC_SYMTAB 0x2 uint32_t cmd; // Размер структуры symtab_command — 0x18 uint32_t cmdsize; // Смещение к таблице символов — 0x4b78e20 uint32_t symoff; // Количество символов в таблице символов — 0x4a537 uint32_t nsyms; // Смещение таблицы строк — 0x5020cc0 uint32_t stroff; // Размер таблицы строк в байтах — 0xa37f50 uint32_t strsize;};
Источник: xakep.ru