Чтение дизассемблированного кода, когда вы годами не писали ассемблер
Вам не нужно заново изучать ассемблер по учебнику, чтобы стать продуктивным в чтении дизассемблированного кода — нужно распознавать горстку повторяющихся паттернов.
Сначала декомпилированный псевдокод, потом сырой ассемблер
Современные инструменты (Ghidra, декомпилятор Hex-Rays от IDA) автоматически конвертируют ассемблер обратно в псевдокод, похожий на C. Начинайте оттуда — это гораздо более читаемо, чем сырой ассемблер, и для большинства задач анализа достаточно точно само по себе.
Переходите к сырому ассемблеру конкретно когда: вывод декомпилятора выглядит неправильным или неполным, вам нужно точное поведение на уровне инструкций (тайминг, конкретное использование регистров), или вы имеете дело с сильно оптимизированным или обфусцированным кодом, где декомпилятор испытывает трудности.
Паттерны, которые стоит распознавать, а не запоминать
- Пролог/эпилог функции — узнаваемая последовательность в начале и конце большинства функций, которая настраивает и разбирает кадр стека. Вы будете видеть одну и ту же форму снова и снова; её распознавание говорит вам «функция начинается/заканчивается здесь» без понимания каждой инструкции в ней.
- Сравнения, за которыми следуют условные переходы — так выглядит оператор
ifна уровне ассемблера. Инструкция сравнения, за которой следует переход-если-равно (или подобное), — это версия ветвления в дизассемблированном коде. - Циклы как переход назад к более раннему адресу — цикл в исходном коде становится, на уровне ассемблера, сравнением и переходом назад к точке раньше в коде.
- Ссылки на строки — декомпиляторы и дизассемблеры обычно показывают вам фактическую строку, на которую ссылается фрагмент кода (сообщение об ошибке, URL, строка формата). Это часто самый быстрый способ сориентироваться в незнакомом коде — найдите знакомую строку и работайте наружу оттуда.
Практическая стратегия чтения
- Сначала смотрите на декомпилированный псевдокод, а не на сырой ассемблер.
- Находите узнаваемые строки и работайте назад от места, где на них ссылаются — это часто приводит вас к релевантной логике быстрее, чем чтение сверху вниз.
- Следуйте за вызовами функций наружу от точки входа, а не читайте каждую функцию изолированно — контекст от вызывающего часто проясняет, что делает функция, ещё до того, как вы полностью прочитали её тело.
- Сравнивайте с кодом, который вы написали сами (см. наше руководство по началу работы с реверс-инжинирингом), когда паттерн выглядит незнакомым — воссоздание минимального примера и компиляция его самостоятельно часто быстрее, чем попытка разобраться в нём только по документации.
Что реально нужно запомнить (короткий список)
Реалистично, только: как выглядит сравнение-затем-переход, как выглядит вызов/возврат функции, и как распознать цикл. Всё остальное — конкретные мнемоники инструкций, соглашения о регистрах — вы усвоите постепенно просто из повторяющегося воздействия, так же как чтение иностранного языка становится легче с объёмом, а не с предварительным запоминанием.
Частые вопросы
Нужно ли мне писать ассемблер, чтобы хорошо его читать?
Нет — чтение и написание — это подлинно разные навыки, и реверс-инжиниринг зависит почти полностью от первого. Вы можете стать продуктивным в чтении дизассемблированного кода, никогда не написав ни строчки ассемблера самостоятельно.
Стоит ли читать сырой ассемблер или декомпилированный псевдокод?
Начинайте с декомпилированного псевдокода (из Ghidra или Hex-Rays от IDA), когда он доступен — его драматически легче читать, и обычно достаточно точен для анализа. Переходите к сырому ассемблеру, когда вывод декомпилятора неясен или когда нужно понять точное низкоуровневое поведение, которое он абстрагирует.
Какой самый быстрый способ освоиться с этим?
Скомпилируйте небольшие, известные программы самостоятельно (см. наше руководство по началу работы с реверс-инжинирингом) и сравните ваш исходный код с дизассемблированным/декомпилированным выводом бок о бок — распознавание паттернов, для которых вы уже знаете ответ, гораздо быстрее, чем попытка разобраться в незнакомом коде с нуля.