Подтвердить что ты не робот

Как разобрать, изменить и затем собрать исполняемый файл Linux?

Можно ли это сделать? Я использовал objdump, но это не создает сборку, которая будет принята любым ассемблером, о котором я знаю. Я хотел бы иметь возможность изменять инструкции в исполняемом файле, а затем проверять его после.

4b9b3361

Ответ 1

Я не думаю, что есть надежный способ сделать это. Форматы машинного кода очень сложны, сложнее, чем файлы сборки. На самом деле невозможно собрать скомпилированный двоичный файл (например, в формате ELF) и создать программу сборки исходного кода, которая будет скомпилирована с тем же (или аналогичным) двоичным кодом. Чтобы получить представление о различиях, сравните вывод компиляции GCC непосредственно с ассемблером (gcc -S) по сравнению с выходом objdump в исполняемом файле (objdump -D).

Есть два серьезных осложнения, о которых я могу думать. Во-первых, сам машинный код не соответствует 1-к-1 соответствию с кодом сборки из-за таких вещей, как смещения указателя.

Например, рассмотрим код C для Hello world:

int main()
{
    printf("Hello, world!\n");
    return 0;
}

Скомпилируется с кодом сборки x86:

.LC0:
    .string "hello"
    .text
<snip>
    movl    $.LC0, %eax
    movl    %eax, (%esp)
    call    printf

Где .LCO - именованная константа, а printf - символ в таблице символов разделяемой библиотеки. Сравните с выходом objdump:

80483cd:       b8 b0 84 04 08          mov    $0x80484b0,%eax
80483d2:       89 04 24                mov    %eax,(%esp)
80483d5:       e8 1a ff ff ff          call   80482f4 <[email protected]>

Во-первых, константа .LC0 теперь является лишь некоторым случайным смещением в памяти где-то - было бы сложно создать исходный файл сборки, который содержит эту константу в правильном месте, поскольку ассемблер и компоновщик могут свободно выбирать местоположения для эти константы.

Во-вторых, я не совсем уверен в этом (и это зависит от таких вещей, как независимый от позиции код), но я считаю, что ссылка на printf на самом деле не закодирована по адресу указателя в этом коде, но ELF заголовки содержат таблицу поиска, которая динамически заменяет свой адрес во время выполнения. Поэтому дизассемблированный код не совсем соответствует коду исходного кода.

В целом, сборка источника имеет символы, тогда как скомпилированный машинный код имеет адреса, которые трудно отменить.

Второе основное осложнение состоит в том, что исходный файл сборки не может содержать всю информацию, которая присутствовала в исходных заголовках файлов ELF, например, какие библиотеки динамически связываются с ними и другие метаданные, размещенные там исходным компилятором. Было бы сложно восстановить это.

Как я уже сказал, возможно, что специальный инструмент может манипулировать всей этой информацией, но маловероятно, что можно просто создать ассемблерный код, который можно повторно собрать обратно в исполняемый файл.

Если вы заинтересованы в изменении только небольшого раздела исполняемого файла, я рекомендую гораздо более тонкий подход, чем перекомпиляцию всего приложения. Используйте objdump для получения кода сборки для интересующей вас функции. Преобразуйте ее в "синтаксис исходной сборки" вручную (и здесь я хочу, чтобы был инструмент, который фактически произвел разборку в том же синтаксисе, что и вход), и изменить его по своему усмотрению. Когда вы закончите, перекомпилируйте только эти функции и используйте objdump для определения машинного кода для вашей измененной программы. Затем используйте шестнадцатеричный редактор, чтобы вручную вставить новый машинный код в верхней части соответствующей части оригинальной программы, заботясь о том, чтобы ваш новый код был точно таким же количеством байтов, что и старый код (или все смещения были бы неправильными). Если новый код короче, вы можете его использовать, используя инструкции NOP. Если он длиннее, у вас могут быть проблемы и, возможно, придется создавать новые функции и вызывать их вместо этого.

Ответ 2

Для изменения кода внутри двоичной сборки обычно есть 3 способа сделать это.

  • Если это всего лишь тривиальная вещь, такая как константа, то вы просто меняете местоположение с помощью шестнадцатеричного редактора. Предполагая, что вы можете найти его для начала.
  • Если вам нужно изменить код, используйте LD_PRELOAD для перезаписи некоторых функций в вашей программе. Это не работает, если функция не находится в таблицах функций.
  • Взломайте код в функции, которую вы хотите исправить, для прямого перехода на функцию, которую вы загружаете через LD_PRELOAD, а затем переходите в одно и то же место (это комбинация из двух предыдущих).

Конечно, будет работать только второй, если сборка выполняет какую-либо проверку целостности.

Изменить: если это не очевидно, то игра с бинарными сборками - это ОЧЕНЬ высокоуровневый материал для разработчиков, и вам будет сложно рассказать об этом здесь, если только это не конкретные вещи, о которых вы просите.

Ответ 3

@mgiuca правильно рассмотрел этот ответ с технической точки зрения. Фактически, дизассемблирование исполняемой программы в простой для перекомпиляции источник сборки - непростая задача.

Чтобы добавить некоторые фрагменты в обсуждение, есть несколько методов/инструментов, которые могут быть интересны для изучения, хотя они технически сложны.

  • Статическая/динамическая аппаратура. Этот метод предполагает анализ исполняемого формата, вставку/удаление/замену определенных инструкций по сборке для определенной цели, исправление всех ссылок на переменные/функции в исполняемом файле и испускание нового модифицированного исполняемого файла. Некоторые инструменты, о которых я знаю, являются: PIN, Hijacker, PEBIL, DynamoRIO. Учтите, что настройка таких инструментов с целью, отличной от той, для которой они были разработаны, может быть сложной и требует понимания как исполняемых форматов, так и наборов инструкций.
  • Полная исполняемая декомпиляция. Этот метод пытается восстановить полный источник сборки из исполняемого файла. Возможно, вам стоит взглянуть на Online Disassembler, который пытается выполнить эту работу. Вы теряете информацию о разных исходных модулях и возможно именах функций/переменных.
  • Декомпиляция с перераспределением. Этот метод пытается извлечь дополнительную информацию из исполняемого файла, глядя на отпечатки от компилятора (т.е. Шаблоны кода, созданные известными компиляторами) и другие детерминированные вещи. Основная цель - восстановить исходный код более высокого уровня, такой как источник C, из исполняемого файла. Иногда это может восстановить информацию о именах функций/переменных. Считайте, что сбор источников с -g часто дает лучшие результаты. Возможно, вы захотите дать Retargetable Decompiler попробовать.

Большая часть этого исходит из областей анализа оценки уязвимостей и анализа выполнения. Это сложные методы, и зачастую инструменты не могут быть использованы сразу же из коробки. Тем не менее, они оказывают неоценимую помощь при попытке перепроектировать какое-то программное обеспечение.

Ответ 4

миазм

https://github.com/cea-sec/miasm

Это представляется наиболее перспективным конкретным решением. Согласно описанию проекта, библиотека может:

  • Открытие/изменение/создание PE/ELF 32/64 LE/BE с использованием Elfesteem
  • Сборка/дизассемблирование X86/ARM/MIPS/SH4/MSP430

Поэтому он должен в основном:

  • разобрать ELF во внутреннее представление (разборка)
  • изменить то, что вы хотите.
  • создать новый ELF (сборка)

Я не думаю, что он генерирует текстовое представление разборки, вам, вероятно, придется пройти через структуры данных Python.

TODO найти минимальный пример того, как делать все это с помощью библиотеки. Хорошей отправной точкой является example/disasm/full.py, который анализирует данный файл ELF. Ключевая структура верхнего уровня Container, которая читает файл ELF с помощью Container.from_stream. TODO, как собрать его впоследствии? Эта статья, похоже, делает это: http://www.miasm.re/blog/2016/03/24/re150_rebuild.html

В этом вопросе спрашивается, есть ли другие такие библиотеки: https://reverseengineering.stackexchange.com/questions/1843/what-are-the-available-libraries-to-statically-modify-elf-executables

Похожие вопросы:

Я думаю, что эта проблема не автоматизируется

Я думаю, что общая проблема не полностью автоматизирована, и общее решение в основном эквивалентно "как реконструировать" двоичный файл.

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

В формальных выражениях нам нужно извлечь граф потока управления двоичного кода.

Однако, с непрямыми ветвями, например https://en.wikipedia.org/wiki/Indirect_branch, определить этот граф непросто, см. также: Расчет назначения косвенного перехода

Ответ 5

Еще одна вещь, которая вам может быть интересна:

  • двоичная аппаратура - изменение существующего кода

Если интересно, проверьте: Pin, Valgrind (или проекты делают это: NaCl - собственный клиент Google, возможно, QEmu.)

Ответ 6

Вы можете запустить исполняемый файл под контролем ptrace (другими словами, отладчик, такой как gdb), и таким образом управлять выполнением по ходу, не изменяя фактический файл. Конечно, требует обычных навыков редактирования, таких как поиск, где конкретные инструкции, на которые вы хотите повлиять, находятся в исполняемом файле.