Заблудившиеся символы. Чиним отладочные данные и восстанавливаем читаемость бинаря

Се­год­ня мы раз­берем­ся, как при­выч­ная отла­доч­ная информа­ция может обер­нуть­ся неожи­дан­ной голов­ной болью при ревер­се. Возь­мем бинарь 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

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *