stihl не предоставил(а) никакой дополнительной информации.
IDA — вещь удобная, но далеко не всесильная. Что делать, если тебе попался бинарник под экзотический процессор вроде CRIS, а IDA лишь развела руками? В этой статье мы разберемся, как реверсить такой код с минимальными затратами времени и нервов, не конструируя собственный дизассемблер и не собирая модуль для IDA, а немного схитрив — и все равно получив достойный результат.
В своих статьях я редко уделяю внимание необычным процессорам. Большинство исследователей уверены, что IDA всегда знает целевой процессор как родной, ну или его хотя бы можно заменить аналогичным, систему команд которого IDA уж точно понимает. К сожалению, это совсем не аксиома, IDA не всесильна, а в мире существует и активно используется множество процессоров, о которых эта программа до сих пор не имеет ни малейшего представления и навряд ли когда‑либо включит их поддержку.
Но ведь работать с ними как‑то надо! Как выкрутиться простому хакеру без столь удобного инструмента, кажущегося незаменимым? Попробуем разобрать все мытарства, ожидающие его на этом тернистом пути.
Итак, условие задачи: дана некая библиотека, определенную функциональность которой нам нужно постичь и реверсировать. Как обычно, для начала загружаем ее в Detect It Easy.
Для просмотра ссылки Войдиили Зарегистрируйся
Сразу замечаем неладное: тип процессора — 32-битный CRIS — нам отродясь не встречался. Пробуем дизассемблировать библиотеку в IDA.
Для просмотра ссылки Войдиили Зарегистрируйся
IDA вроде соглашается с DIE: тип файла указан как ELF for CRIS, однако предлагаемый по умолчанию тип процессора (MetaPC) настораживает. Тем более никакого CRIS в списке поддерживаемых IDA процессоров и близко нет. Естественно, следом выскакивает ошибка.
Для просмотра ссылки Войдиили Зарегистрируйся
Что такое тип 76, в принципе, понятно — мгновенно нагуглив спецификацию хидера ELF, мы находим его расшифровку:
EM_CRIS 76 Axis Communications 32-bit embedded processor
Семейство довольно известное и широко используемое в узких кругах: обычно под этим типом подразумевают Для просмотра ссылки Войдиили Зарегистрируйся. Само слово CRIS — это тип архитектуры подобных процессоров, оно является сокращением от Code Reduced Instruction Set, что переводится как «набор инструкций с сокращенным кодом».
Этот набор инструкций заявлен как подмножество инструкций RISC, однако при попытке выбора в IDA любого из похожих типов (да и вообще любого поддерживаемого IDA типа) осмысленного кода получить не удается.
Для просмотра ссылки Войдиили Зарегистрируйся
Попробуем эмпирически проверить, что все‑таки у нас за процессор и как раскодировать его инструкции. Google показывает нам спецификацию похожего CRIS-процессора Для просмотра ссылки Войдиили Зарегистрируйся. Посмотрим, соответствуют ли инструкции нашего процессора указанной спецификации.
Для этого попробуем раскодировать первую инструкцию _init_proc со скриншота выше, опираясь на найденную спецификацию. Согласно ей, почти все инструкции этого процессора имеют длину 16 бит (Dword). Соответственно, раскодируемое значение будет 0xE284. В битовом виде это будет выглядеть так:
В разделе 3 INSTRUCTIONS IN ALPHABETICAL ORDER нашей спецификации прилагаются побитовые расшифровки каждого класса инструкций этого процессора. Изрядно помаявшись, мы находим инструкцию, подходящую под нашу битовую маску, subq.
Для просмотра ссылки Войдиили Зарегистрируйся
Если верить спецификации, это операция быстрого вычитания 6-битной числовой константы из регистра. Константа определяется младшими шестью битами и в нашем случае равна 00100b=4. Регистр Rd же — старшие 4 бита 1110b=14, что соответствует указателю стека sp. То есть полная мнемоника нашей команды subq 4, sp (переместить указатель стека на 4 байта вверх) вполне похожа на истину — обычно так начинается стандартный вход в процедуру с резервированием пула локальных переменных. Спецификация, похоже, рабочая, но не будем же мы покомандно расшифровывать несколько сот килобайт кода? Жизнь слишком коротка для подобных челленджей, надо как‑то автоматизировать процесс.
Если ты по жизни максималист и обладаешь неограниченным запасом свободного времени при полном отсутствии других интересных задач, можно сделать собственный уникальный дизассемблер‑декомпилятор, основываясь на нагугленной спецификации. Я не осуждаю подобное решение, у него множество плюсов — можно потренироваться в парсинге и глубже понять принцип действия процессора. В конце концов, это позволит создать новый, единственный в своем роде гитхабовский проект, на который потом можно наращивать новые файловые форматы и процессорные архитектуры. Его не стыдно будет положить в портфолио и публиковать о нем статьи на «Хакере» и прочих умных ресурсах.
Но цель нашей сегодняшней статьи совсем другая — выбрать путь, который позволит быстро и наименьшими усилиями реверсировать фрагмент кода редкого (и уже винтажного) процессора. А писать под такое собственный декомпилятор — это, извиняюсь за неприличное сравнение, все равно что выкупать гектар тайги и строить на нем бумажный завод для того, чтобы скастовать рулон туалетной бумаги.
Попробуем минимизировать задачу — у нас имеется IDA, которая уже умеет разбирать (и успешно разобрала) формат ELF со всеми его строками, функциями, импортируемыми‑экспортируемыми символами и прочими ништяками. Нам не хватает только одного: парсинга ассемблерных инструкций.
С другой стороны, IDA расширяема, к ней вполне можно написать свой процессорный модуль. Ты, вероятно, знаешь, что IDA позволяет писать Для просмотра ссылки Войдиили Зарегистрируйся. Задача эта, хоть и полегче разработки собственного декомпилятора с нуля, тоже весьма непростая.
Если ты собираешься компилировать собственный нативный процессорный модуль для IDA, сложности начинаются уже на этапе получения Hex-Rays SDK под нужную (а надо сказать, что версии SDK довольно слабо совместимы друг с другом) версию дизассемблера — это весьма неприятный квест. Затем начнутся сложности с компилятором С++, потому что далеко не каждый из них справится с этим нелегким делом. Поэтому нужно заранее готовить бубен побольше.
Разработчики IDA явно рассчитывали на продвинутую хакерскую аудиторию, поэтому, чтобы не разочаровывать ее, сделали этот путь максимально хардкорным, вдобавок практически не приложив к нему толковой документации. Впрочем, разработчики сторонних плагинов написали множество статей об этом: например, статью с говорящим названием Для просмотра ссылки Войдиили Зарегистрируйся, «Для просмотра ссылки Войди или Зарегистрируйся» или «Для просмотра ссылки Войди или Зарегистрируйся».
Отсутствие SDK можно компенсировать, заменив нативный плагин питоновским скриптом. Да, так тоже можно, и подтверждением тому служат следующие ссылки.
А во‑вторых, беглый гуглинг показывает, что эту задачу девять лет назад уже Для просмотра ссылки Войдиили Зарегистрируйся. Если ты был столь нетерпелив, что, не дочитав это предложение до конца, успел найти SDK под версию IDA 7.2 и начал собирать проект, то, вероятно, заметил, что он не компилируется. Попробуем разобраться, почему так происходит и вообще нужно ли нам это всё. Проверять будем по уже обкатанной выше схеме — на первой инструкции _init_proc 0xE284 (интерпретированной нами как subq 4, sp). Откроем модуль анализа инструкций ana.cpp:
Мы видим, что анализ реализован следующим образом: из 16-битной команды выбираются средние 8 байт (с 4-го по 11-й включительно) и по ним делается длинный switch. Маска нашей искомой команды — 0x28=40, посмотрим, как она обрабатывается в этом коде:
В принципе, все соответствует нашей спецификации: коды 40–43 соответствуют инструкции SUBQ, однако дальше начинается самое интересное — метка перехода extract_sfmt_addq отсутствует в данном модуле, да и во всем проекте. Код явно неполон.
Снова обратившись к гуглу, мы внезапно обнаруживаем Для просмотра ссылки Войдиили Зарегистрируйся. Если помнишь, отладчик этот мы обсуждали в недавней статье «Для просмотра ссылки Войди или Зарегистрируйся». Похоже на то, что автор вышеупомянутого недоплагина брал этот код за основу и частично перенес его в проект, но оказался недостаточно усидчив, дабы закончить начатое. Ты вполне можешь закончить проект за него на досуге, но сейчас у нас нет на это времени: продолжаем искать более простые пути.
Кстати, GDB и QEMU могут быть одним из возможных решений! В Для просмотра ссылки Войдиили Зарегистрируйся реализован альтернативный подход к парсингу и декодированию инструкций CRIS. Его тоже можно использовать при написании собственного декомпилятора, и он, на мой взгляд, более вменяемый. А можно найти образ «Линукса» под эту платформу с нужными утилитами и попробовать декомпилировать наш ELF-модуль прямо из него, например через тот же GDB или objdump.
К сожалению, при кажущейся простоте лично у меня этот способ забуксовал уже на этапе поиска QEMU и образа системы под данную платформу — все‑таки это изрядная экзотика. Возможно, тебе повезет больше, мы же продолжаем дальше поиск более простых путей решения поставленной задачи.
Оказывается, наш процессор есть в списке поддерживаемых у Для просмотра ссылки Войдиили Зарегистрируйся. Для удобства рассмотрим основанный на этом фреймворке набор Для просмотра ссылки Войди или Зарегистрируйся. Сразу предупреждаю: это не милая и удобная IDA, это суровый юниксоподобный дизассемблер — отладчик командной строки вроде GDB. Причем если у GDB интуитивность интерфейса перпендикулярна логике здорового человека, то у Rizin логика работы примерно уровня каких‑нибудь разумных грибов с далекой планеты, враждебных всему человечеству.
Ну, например, в комплект поставки входит Для просмотра ссылки Войдиили Зарегистрируйся, но она умеет дизассемблировать только код, введенный в текстовой шестнадцатеричной строке, да и то не полностью. У этого инструмента даже есть GUI — cutter, который наконец‑то дизассемблировал нашу многострадальную библиотеку в нормальном виде.
Для просмотра ссылки Войдиили Зарегистрируйся
Но пользоваться таким дизассемблером для реверса чудовищно неудобно — нет даже перехода по ссылкам. Да что там говорить, внезапно оказалось, что авторам дизассемблера выгрузка дизассемблированного кода в текстовый файл совершенно не нужна. «Экспортировать как код» в их представлении — выгрузка в массив байтов почему‑то в формате нескольких малоизвестных языков программирования. Причем через клипборд код можно копировать только мелкими частями, поэтому после долгого чтения мануалов мне таки удалось выгрузить код из консоли в текстовый файл вот таким колдунским заклинанием:
Сделал я это потому, что мне хотелось поскорее покинуть это порождение темного разума и как‑то загнать полученный ассемблерный листинг в привычную и удобную IDA, чтобы продолжить анализ в более комфортной обстановке.
Поскольку нам лениво полностью описывать целевой процессор со всеми его командами и регистрами, попробуем смухлевать: представим весь дизассемблированный IDA код как сырой массив слепых данных и заново разметим его комментариями из полученного ассемблерного листинга. Для этого снова обратимся к питоновскому скриптингу. Формат нашего листинга выглядит так:
Напишем простенький скрипт, построчно читающий файл, выделяя из каждой строки адрес и соответствующее ему описание инструкции, которое в итоге будет комментировать данные по этому адресу в IDA:
i
Запустив этот скрипт, мы получаем следующий комментированный код.
Для просмотра ссылки Войдиили Зарегистрируйся
Конечно, это не такой идеальный исходник, как живой код, полученный при честном дизассемблировании собственным процессорным модулем. Более того, виден результат мухлежа с длиной команды: часть команд все‑таки длиннее 16 бит, и эти места выглядят как дырки в коде, режущие взгляд истинного перфекциониста. Которому я и предоставляю право самостоятельно поправить код скрипта так, чтобы комментировалось именно то количество байтов, которое соответствует данной команде.
А мы, собственно, добились своей цели — малой кровью затащили дизассемблированный код в удобный инструмент для реверса, в котором этот самый код гораздо удобнее анализировать, чем в cutter. Я, разумеется, не претендую на оптимальное решение задачи: возможно, кому‑то из читателей больше понравится какой‑то другой способ из ранее упомянутых. Но свобода выбора всегда остается за тобой.
В своих статьях я редко уделяю внимание необычным процессорам. Большинство исследователей уверены, что IDA всегда знает целевой процессор как родной, ну или его хотя бы можно заменить аналогичным, систему команд которого IDA уж точно понимает. К сожалению, это совсем не аксиома, IDA не всесильна, а в мире существует и активно используется множество процессоров, о которых эта программа до сих пор не имеет ни малейшего представления и навряд ли когда‑либо включит их поддержку.
Но ведь работать с ними как‑то надо! Как выкрутиться простому хакеру без столь удобного инструмента, кажущегося незаменимым? Попробуем разобрать все мытарства, ожидающие его на этом тернистом пути.
Итак, условие задачи: дана некая библиотека, определенную функциональность которой нам нужно постичь и реверсировать. Как обычно, для начала загружаем ее в Detect It Easy.
Для просмотра ссылки Войди
Сразу замечаем неладное: тип процессора — 32-битный CRIS — нам отродясь не встречался. Пробуем дизассемблировать библиотеку в IDA.
Для просмотра ссылки Войди
IDA вроде соглашается с DIE: тип файла указан как ELF for CRIS, однако предлагаемый по умолчанию тип процессора (MetaPC) настораживает. Тем более никакого CRIS в списке поддерживаемых IDA процессоров и близко нет. Естественно, следом выскакивает ошибка.
Для просмотра ссылки Войди
Что такое тип 76, в принципе, понятно — мгновенно нагуглив спецификацию хидера ELF, мы находим его расшифровку:
EM_CRIS 76 Axis Communications 32-bit embedded processor
Семейство довольно известное и широко используемое в узких кругах: обычно под этим типом подразумевают Для просмотра ссылки Войди
Этот набор инструкций заявлен как подмножество инструкций RISC, однако при попытке выбора в IDA любого из похожих типов (да и вообще любого поддерживаемого IDA типа) осмысленного кода получить не удается.
Для просмотра ссылки Войди
Попробуем эмпирически проверить, что все‑таки у нас за процессор и как раскодировать его инструкции. Google показывает нам спецификацию похожего CRIS-процессора Для просмотра ссылки Войди
Для этого попробуем раскодировать первую инструкцию _init_proc со скриншота выше, опираясь на найденную спецификацию. Согласно ей, почти все инструкции этого процессора имеют длину 16 бит (Dword). Соответственно, раскодируемое значение будет 0xE284. В битовом виде это будет выглядеть так:
1110 0010 1000 0100
В разделе 3 INSTRUCTIONS IN ALPHABETICAL ORDER нашей спецификации прилагаются побитовые расшифровки каждого класса инструкций этого процессора. Изрядно помаявшись, мы находим инструкцию, подходящую под нашу битовую маску, subq.
Для просмотра ссылки Войди
Если верить спецификации, это операция быстрого вычитания 6-битной числовой константы из регистра. Константа определяется младшими шестью битами и в нашем случае равна 00100b=4. Регистр Rd же — старшие 4 бита 1110b=14, что соответствует указателю стека sp. То есть полная мнемоника нашей команды subq 4, sp (переместить указатель стека на 4 байта вверх) вполне похожа на истину — обычно так начинается стандартный вход в процедуру с резервированием пула локальных переменных. Спецификация, похоже, рабочая, но не будем же мы покомандно расшифровывать несколько сот килобайт кода? Жизнь слишком коротка для подобных челленджей, надо как‑то автоматизировать процесс.
Если ты по жизни максималист и обладаешь неограниченным запасом свободного времени при полном отсутствии других интересных задач, можно сделать собственный уникальный дизассемблер‑декомпилятор, основываясь на нагугленной спецификации. Я не осуждаю подобное решение, у него множество плюсов — можно потренироваться в парсинге и глубже понять принцип действия процессора. В конце концов, это позволит создать новый, единственный в своем роде гитхабовский проект, на который потом можно наращивать новые файловые форматы и процессорные архитектуры. Его не стыдно будет положить в портфолио и публиковать о нем статьи на «Хакере» и прочих умных ресурсах.
Но цель нашей сегодняшней статьи совсем другая — выбрать путь, который позволит быстро и наименьшими усилиями реверсировать фрагмент кода редкого (и уже винтажного) процессора. А писать под такое собственный декомпилятор — это, извиняюсь за неприличное сравнение, все равно что выкупать гектар тайги и строить на нем бумажный завод для того, чтобы скастовать рулон туалетной бумаги.
Попробуем минимизировать задачу — у нас имеется IDA, которая уже умеет разбирать (и успешно разобрала) формат ELF со всеми его строками, функциями, импортируемыми‑экспортируемыми символами и прочими ништяками. Нам не хватает только одного: парсинга ассемблерных инструкций.
С другой стороны, IDA расширяема, к ней вполне можно написать свой процессорный модуль. Ты, вероятно, знаешь, что IDA позволяет писать Для просмотра ссылки Войди
Если ты собираешься компилировать собственный нативный процессорный модуль для IDA, сложности начинаются уже на этапе получения Hex-Rays SDK под нужную (а надо сказать, что версии SDK довольно слабо совместимы друг с другом) версию дизассемблера — это весьма неприятный квест. Затем начнутся сложности с компилятором С++, потому что далеко не каждый из них справится с этим нелегким делом. Поэтому нужно заранее готовить бубен побольше.
Разработчики IDA явно рассчитывали на продвинутую хакерскую аудиторию, поэтому, чтобы не разочаровывать ее, сделали этот путь максимально хардкорным, вдобавок практически не приложив к нему толковой документации. Впрочем, разработчики сторонних плагинов написали множество статей об этом: например, статью с говорящим названием Для просмотра ссылки Войди
Отсутствие SDK можно компенсировать, заменив нативный плагин питоновским скриптом. Да, так тоже можно, и подтверждением тому служат следующие ссылки.
www
- Для просмотра ссылки Войди
или Зарегистрируйся; - Для просмотра ссылки Войди
или Зарегистрируйся; - Для просмотра ссылки Войди
или Зарегистрируйся.
А во‑вторых, беглый гуглинг показывает, что эту задачу девять лет назад уже Для просмотра ссылки Войди
Код:
/* Analyze the current instruction. */
int idaapi ana( void )
{
/* temporary buffer */
unsigned char buffer[2];
/* Result of decoder. */
CRIS_INSN_TYPE itype;
CGEN_INSN_WORD insn;
CGEN_INSN_WORD entire_insn;
ea_t pc;
get_data_value(cmd.ea, (uval_t *)buffer, 2);
insn = get_insn_value(buffer, 16);
entire_insn = get_insn_value(buffer, 16);
pc = cmd.ea;
{
{
unsigned int val = (((insn >> 4) & (255 << 0)));
switch (val)
{
case 0 : /* fall through */
case 1 : /* fall through */
...
Мы видим, что анализ реализован следующим образом: из 16-битной команды выбираются средние 8 байт (с 4-го по 11-й включительно) и по ним делается длинный switch. Маска нашей искомой команды — 0x28=40, посмотрим, как она обрабатывается в этом коде:
Код:
...
case 40 : /* fall through */
case 41 : /* fall through */
case 42 : /* fall through */
case 43 : itype = CRIS_INSN_SUBQ; goto extract_sfmt_addq;
...
В принципе, все соответствует нашей спецификации: коды 40–43 соответствуют инструкции SUBQ, однако дальше начинается самое интересное — метка перехода extract_sfmt_addq отсутствует в данном модуле, да и во всем проекте. Код явно неполон.
Снова обратившись к гуглу, мы внезапно обнаруживаем Для просмотра ссылки Войди
Кстати, GDB и QEMU могут быть одним из возможных решений! В Для просмотра ссылки Войди
К сожалению, при кажущейся простоте лично у меня этот способ забуксовал уже на этапе поиска QEMU и образа системы под данную платформу — все‑таки это изрядная экзотика. Возможно, тебе повезет больше, мы же продолжаем дальше поиск более простых путей решения поставленной задачи.
Оказывается, наш процессор есть в списке поддерживаемых у Для просмотра ссылки Войди
Ну, например, в комплект поставки входит Для просмотра ссылки Войди
Для просмотра ссылки Войди
Но пользоваться таким дизассемблером для реверса чудовищно неудобно — нет даже перехода по ссылкам. Да что там говорить, внезапно оказалось, что авторам дизассемблера выгрузка дизассемблированного кода в текстовый файл совершенно не нужна. «Экспортировать как код» в их представлении — выгрузка в массив байтов почему‑то в формате нескольких малоизвестных языков программирования. Причем через клипборд код можно копировать только мелкими частями, поэтому после долгого чтения мануалов мне таки удалось выгрузить код из консоли в текстовый файл вот таким колдунским заклинанием:
pd 100000 @ 0x000817d8 >> test.asm
Сделал я это потому, что мне хотелось поскорее покинуть это порождение темного разума и как‑то загнать полученный ассемблерный листинг в привычную и удобную IDA, чтобы продолжить анализ в более комфортной обстановке.
Поскольку нам лениво полностью описывать целевой процессор со всеми его командами и регистрами, попробуем смухлевать: представим весь дизассемблированный IDA код как сырой массив слепых данных и заново разметим его комментариями из полученного ассемблерного листинга. Для этого снова обратимся к питоновскому скриптингу. Формат нашего листинга выглядит так:
Код:
;-- section..init:
0x000817d8 subq 4, sp ; [09] -r-x section size 40 named .init
0x000817da move.d r1, [sp]
0x000817dc move srp, r1
0x000817de push r0
0x000817e2 move.d pc, r0
0x000817e4 sub.d 0xfffabc8c, r0
0x000817ea jsr 0x000821b2
│ 0x000817f0 jsr 0x000be7c0
││ 0x000817f6 pop r0
││ 0x000817f8 move r1, srp
││ 0x000817fa pop r1
││ 0x000817fc ret ; 0xe2041d0b
││ 0x000817fe nop
││ ;-- section..plt:
││ 0x00081800 push dcr1/mof ; [10] -r-x section size 2320 named .plt
││ 0x00081804 move [0x000d5b5c], dcr1/mof ; 0x157360
││ 0x0008180c jump [0x000d5b60] ; 0x15736c
││ ;-- read:
││ 0x00081814 jump [0x000d5b64] ; 0x157378
...
Напишем простенький скрипт, построчно читающий файл, выделяя из каждой строки адрес и соответствующее ему описание инструкции, которое в итоге будет комментировать данные по этому адресу в IDA:
i
Код:
mport ida_bytes
# Для начала очищаем весь код от неправильно дизассемблированного IDA мусора
address_to_undefine = 0x817D8
size_to_undefine = 0xD60F8
for i in range(address_to_undefine,size_to_undefine):
ida_bytes.del_items(i)
with open("test.asm","r") as file: # Открываем файл листинга
for line in file: # и читаем его построчно
rows=line.split() # Разбиваем считанную строку на слова, разделенные пробелами
k=-1
if rows[0].startswith("0x0"): # Шестнадцатеричный адрес в первом слове?
k=0;
elif rows[1].startswith("0x0"): # А может, во втором?
k=1;
if k>=0: # Если он вообще там есть
Ofs=int(rows[k], 16) # Преобразуем его в число
comment=""
for i in range(k+1,len(rows)): # Объединяем все следующие за ним слова в единую строку комментария
comment+=" "+rows
ida_bytes.create_16bit_data(Ofs,2) # В основном команды 16-битные
idc.set_cmt(Ofs, comment, 1) # поэтому комментируем первое 16-битное слово по считанному адресу
Запустив этот скрипт, мы получаем следующий комментированный код.
Для просмотра ссылки Войди
Конечно, это не такой идеальный исходник, как живой код, полученный при честном дизассемблировании собственным процессорным модулем. Более того, виден результат мухлежа с длиной команды: часть команд все‑таки длиннее 16 бит, и эти места выглядят как дырки в коде, режущие взгляд истинного перфекциониста. Которому я и предоставляю право самостоятельно поправить код скрипта так, чтобы комментировалось именно то количество байтов, которое соответствует данной команде.
А мы, собственно, добились своей цели — малой кровью затащили дизассемблированный код в удобный инструмент для реверса, в котором этот самый код гораздо удобнее анализировать, чем в cutter. Я, разумеется, не претендую на оптимальное решение задачи: возможно, кому‑то из читателей больше понравится какой‑то другой способ из ранее упомянутых. Но свобода выбора всегда остается за тобой.