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

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


person FlagCapper    schedule 30.11.2010    source источник
comment
Эр. Это Linux с открытым исходным кодом, что обычно означает, что исходный код находится в свободном доступе.   -  person James Anderson    schedule 30.11.2010
comment
@James Anderson Не обязательно верно для всех исполняемых файлов, с которыми вы когда-либо сталкивались.   -  person mgiuca    schedule 30.11.2010
comment
@JamesAnderson, в настоящее время я просматриваю код с открытым исходным кодом, который был написан для коммерческого компилятора и не компилируется в gcc.   -  person avakar    schedule 10.11.2012
comment
На других сайтах: reverseengineering.stackexchange.com/questions/185/ | askubuntu.com/questions/617441 /   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 03.04.2017
comment
Вы можете использовать Radare2, открыв его в режиме записи с флагом -w. github.com/radare/radare2/blob/master/doc/intro. Мэриленд   -  person rmn    schedule 15.03.2019


Ответы (8)


Я не думаю, что есть какой-либо надежный способ сделать это. Форматы машинного кода очень сложны, сложнее, чем ассемблерные файлы. На самом деле невозможно взять скомпилированный двоичный файл (скажем, в формате 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 <printf@plt>

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

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

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

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

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

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

person mgiuca    schedule 30.11.2010
comment
Если символы не удалены намеренно, многие из них сохраняются. Попробуйте nm /bin/ls. - person Praxeolitic; 16.10.2014
comment
Я думаю, вы преувеличиваете некоторые трудности кода, чтобы найти свои константы. Основная проблема заключается в том, что ни один синтаксис ассемблера не может однозначно представлять кодировки разной длины для одной и той же инструкции. objconv дизассемблер Agner Fog дизассемблирует в NASM, YASM, MASM или GNU в качестве синтаксиса. Этот вывод можно собрать обратно в аналогичный двоичный файл, но любые предположения, сделанные кодом о выравнивании/смещении, могли измениться. например PLT (таблица связывания процедур) должна использовать кодировку jmp rel32, чтобы правильное смещение можно было заполнить во время выполнения. - person Peter Cordes; 10.02.2016
comment
Обновление: у GAS есть префиксы, которые могут запрашивать явный disp32, даже если инструкция в этом не нуждается: like-s-in-x86-mov-s-documented" title="где находятся суффиксы инструкций ассемблера gnu, такие как s в документированных x86 mov s"> stackoverflow.com/questions/47673177/. Кроме того, NASM обычно может использовать mov eax, [rdi + strict dword 0] для принудительного создания файла disp32. Проблема в том, что дизассемблеры не используют эти переопределения при дизассемблировании более длинных, чем необходимо, инструкций. - person Peter Cordes; 07.12.2017
comment
Питер Кордес, конечно, синтаксис ассемблера может уникальным образом представлять разные кодировки для одной и той же инструкции, разные инструкции с одним и тем же результатом. Я разработал такой синтаксис с целью обратной разработки. Смотри мой ответ - person Albert van der Horst; 16.12.2018
comment
@Peter Cordes Что касается отсутствия перезаписей, это просто означает, что дизассемблер не подходит, а не то, что его может не быть. Смотрите также мой ответ. Если вам нужен инструмент, обладающий волшебным знанием особенностей исполняемого файла, это явно невозможно. Если инструмент имеет возможность сценария для извлечения имен для исполняемого файла (даже если они закодированы по методу Хаффмана, как в случае с одним из моих примеров) и точно различает все возможные выражения одной и той же функции, тогда небо Лимит. Гротьес Альберт - person Albert van der Horst; 11.05.2019
comment
Здесь вы можете найти пример изменения только небольшого раздела исполняемого файла, как обсуждалось в ответе: gist.github .com/simonemainardi/e63c03d6cefe65d0e2541a67e82955f9 - person simonemainardi; 02.05.2020

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

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

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

Вы всегда должны иметь возможность избежать удаления байтов. Добавление байтов может быть необходимо для более сложных модификаций, и это становится намного сложнее.

Шаг 0 (подготовка)

После того, как вы фактически правильно дизассемблировали файл с помощью objdump -D или чего-то, что вы обычно используете в первую очередь, чтобы действительно понять его и найти места, которые нужно изменить, вам нужно принять к сведению следующие вещи, чтобы помочь вы найдете правильные байты для изменения:

  1. Адрес (смещение от начала файла) байтов нужно изменить.
  2. Необработанное значение этих байтов в том виде, в котором они есть на данный момент (опция --show-raw-insn для objdump здесь очень полезна).

Вам также необходимо проверить, работает ли hexdump -R в вашей системе. Если нет, то для остальных этих шагов используйте команду xxd или аналогичную вместо hexdump во всех шагах ниже (обратитесь к документации по любому инструменту, который вы используете, я сейчас объясняю только hexdump в этом ответе, потому что это тот Я знаком с).

Шаг 1

Выведите необработанное шестнадцатеричное представление двоичного файла с помощью hexdump -Cv.

Шаг 2

Откройте файл hexdumped и найдите байты по адресу, который вы хотите изменить.

Краткий ускоренный курс в выводе hexdump -Cv:

  1. Крайний левый столбец — это адреса байтов (относительно начала самого двоичного файла, как и в objdump).
  2. Крайний правый столбец (окруженный символами |) представляет собой просто удобочитаемое представление байтов - там записывается символ ASCII, соответствующий каждому байту, а . заменяет все байты, которые не сопоставляются с печатным символом ASCII.
  3. Важный материал находится между ними — каждый байт в виде двух шестнадцатеричных цифр, разделенных пробелами, по 16 байт на строку.

Осторожно: в отличие от objdump -D, который дает вам адрес каждой инструкции и показывает необработанный шестнадцатеричный код инструкции в зависимости от того, как она задокументирована как закодированная, hexdump -Cv выводит каждый байт точно в том порядке, в котором он появляется в файле. Это может немного сбивать с толку, как в первую очередь на машинах, где байты инструкций расположены в противоположном порядке из-за различий в порядке следования байтов, что также может дезориентировать, когда вы ожидаете определенный байт в качестве определенного адреса.

Шаг 3

Измените байты, которые необходимо изменить - вам, очевидно, нужно выяснить исходную кодировку машинных инструкций (а не мнемонику сборки) и вручную записать правильные байты.

Примечание. Вам не нужно изменять удобочитаемое представление в крайнем правом столбце. hexdump проигнорирует его при распаковке.

Шаг 4

Восстановите дамп измененного файла шестнадцатеричного дампа, используя hexdump -R.

Шаг 5 (проверка работоспособности)

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

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

Пример

Вот реальный рабочий пример, когда я недавно модифицировал двоичный файл ARMv8 (с прямым порядком байтов). (Я знаю, что вопрос помечен x86, но у меня нет под рукой примера x86, и основные принципы те же, только инструкции разные.)

В моей ситуации мне нужно было отключить конкретную проверку, которую вы не должны выполнять: в моем примере двоичного кода в выводе objdump --show-raw-insn -d строка, о которой я заботился, выглядела так (одна инструкция до и после дана для контекста):

     f40:   aa1503e3    mov x3, x21
     f44:   97fffeeb    bl  af0 <error@plt>
     f48:   f94013f7    ldr x23, [sp, #32]

Как видите, наша программа успешно завершает работу, переходя в функцию error (которая завершает работу программы). Неприемлемо. Итак, мы собираемся превратить эту инструкцию в запретную операцию. Итак, мы ищем байты 0x97fffeeb по адресу/смещению файла 0xf44.

Вот строка hexdump -Cv, содержащая это смещение.

00000f40  e3 03 15 aa eb fe ff 97  f7 13 40 f9 e8 02 40 39  |..........@...@9|

Обратите внимание, как на самом деле переворачиваются соответствующие байты (кодировка с прямым порядком байтов в архитектуре применяется к машинным инструкциям, как и ко всему остальному) и как это немного неинтуитивно связано с тем, какой байт находится по какому смещению байта:

00000f40  -- -- -- -- eb fe ff 97  -- -- -- -- -- -- -- --  |..........@...@9|
                      ^
                      This is offset f44, holding the least significant byte
                      So the *instruction as a whole* is at the expected offset,
                      just the bytes are flipped around. Of course, whether the
                      order matches or not will vary with the architecture.

Во всяком случае, я знаю из других разборок, что 0xd503201f дизассемблируется в nop, так что это кажется хорошим кандидатом для моей инструкции без операции. Я соответствующим образом изменил строку в файле hexdumped:

00000f40  e3 03 15 aa 1f 20 03 d5  f7 13 40 f9 e8 02 40 39  |..........@...@9|

Сконвертировал обратно в бинарник с hexdump -R, разобрал новый бинарник с objdump --show-raw-insn -d и проверил правильность изменения:

     f40:   aa1503e3    mov x3, x21
     f44:   d503201f    nop
     f48:   f94013f7    ldr x23, [sp, #32]

Затем я запустил двоичный файл и получил желаемое поведение — соответствующая проверка больше не приводила к прерыванию программы.

Модификация машинного кода прошла успешно.

!!! Предупреждение !!!

Или я добился успеха? Вы заметили, что я пропустил в этом примере?

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

Я изменил только последнюю инструкцию в ответвлении об ошибке! Переход в функцию, которая выходит из программы. Но, как вы можете видеть, регистр x3 был изменен mov чуть выше! На самом деле, всего четыре (4) регистра были изменены как часть преамбулы для вызова error, и один регистр был изменен. Вот полный машинный код для этой ветки, начиная с условного перехода через блок if и заканчивая тем, куда переходит переход, если условный if не используется:

     f2c:   350000e8    cbnz    w8, f48
     f30:   b0000002    adrp    x2, 1000
     f34:   91128442    add x2, x2, #0x4a1
     f38:   320003e0    orr w0, wzr, #0x1
     f3c:   2a1f03e1    mov w1, wzr
     f40:   aa1503e3    mov x3, x21
     f44:   97fffeeb    bl  af0 <error@plt>
     f48:   f94013f7    ldr x23, [sp, #32]

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

В моем случае это на самом деле казалось не вызывало никаких проблем. Так что мне повезло. Очень повезло: только после того, как я уже запустил свой модифицированный двоичный файл (который, кстати, был критичным с точки зрения безопасности двоичным файлом: он имел возможность setuid, setgid и изменять Контекст SELinux!) Я понял, что забыл фактически отследить пути кода того, повлияли ли эти изменения регистра на пути кода, которые появились позже!

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

Что, если бы я вызывал функцию, в которой аргументы высыпались из регистров в стек (что очень часто встречается, например, в x86)? Что, если в наборе инструкций, предшествующих условному переходу, на самом деле было несколько условных инструкций (как это часто бывает, например, в более старых версиях ARM)? Я был бы в еще более безрассудно непоследовательном состоянии после того, как сделал это, казалось бы, самое простое изменение!

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

Итак, как нам исправить это более правильно? Читать дальше.

Удаление кода

Чтобы эффективно/логически удалить более одной инструкции, вы можете заменить первую инструкцию, которую хотите удалить, безусловным переходом к первой инструкции в конце удаленных инструкций. Для этого двоичного файла ARMv8 это выглядело так:

     f2c:   14000007    b   f48
     f30:   b0000002    adrp    x2, 1000
     f34:   91128442    add x2, x2, #0x4a1
     f38:   320003e0    orr w0, wzr, #0x1
     f3c:   2a1f03e1    mov w1, wzr
     f40:   aa1503e3    mov x3, x21
     f44:   97fffeeb    bl  af0 <error@plt>
     f48:   f94013f7    ldr x23, [sp, #32]

По сути, вы убиваете код (превращаете его в мертвый код). Боковое примечание: вы можете сделать что-то подобное с литеральными строками, встроенными в двоичный файл: если вы хотите заменить его на меньшую строку, вы почти всегда можете избежать перезаписи строки (включая завершающий нулевой байт, если это C-строка ) и, при необходимости, перезаписать жестко закодированный размер строки в машинном коде, который ее использует.

Вы также можете заменить все ненужные инструкции на no-ops. Другими словами, мы можем превратить ненужный код в то, что называется блокировкой:

     f2c:   d503201f    nop
     f30:   d503201f    nop
     f34:   d503201f    nop
     f38:   d503201f    nop
     f3c:   d503201f    nop
     f40:   d503201f    nop
     f44:   d503201f    nop
     f48:   f94013f7    ldr x23, [sp, #32]

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

Чтобы было ясно, ошибка проста: я ошибся два (2) раза, когда вручную кодировал эту инструкцию безусловного перехода. И это не всегда наша вина: в первый раз это было из-за того, что документация, которая у меня была, была устаревшей/неправильной и в ней говорилось, что один бит игнорируется в кодировке, хотя на самом деле это не так, поэтому я установил его на ноль с первой попытки.

Добавление кода

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

С точки зрения машинного кода это довольно просто: выберите одну инструкцию в том месте, где вы хотите добавить код, и преобразуйте ее в инструкцию перехода к новому коду, который вам нужно добавить (не забудьте добавить инструкции, которые вы таким образом заменены на новый код, если только вам это не нужно для вашей добавленной логики, и чтобы вернуться к инструкции, к которой вы хотите вернуться в конце добавления). По сути, вы вставляете новый код.

Но вам нужно найти место, куда действительно поместить этот новый код, и это сложная часть.

Если вам действительно повезло, вы можете просто добавить новый машинный код в конец файла, и он просто заработает: новый код загрузится вместе с остальными в тот же ожидаемый машинные инструкции, в пространство вашего адресного пространства, которое попадает на страницу памяти, правильно помеченную как исполняемую.

По моему опыту, hexdump -R игнорирует не только самый правый столбец, но и самый левый столбец, так что вы можете буквально просто указать нулевые адреса для всех добавленных вручную строк, и это сработает.

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

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

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

person mtraceur    schedule 01.10.2018
comment
hexdump не узнает -R. Вместо этого мы можем использовать xxd -r для обратного шестнадцатеричного дампа, но остерегайтесь, что objdump не распознает двоичный файл с обратным шестнадцатеричным дампом. - person Abhishek; 12.02.2020
comment
@Abhishek Майн делает. Я вижу проблему - я делаю этот трюк только на своих очень минимальных системах, в которых есть hexdump из BusyBox, который был скомпилирован с FEATURE_HEXDUMP_REVERSE. Я никогда не пробовал xxd -r, потому что это недоступно в системах, с которыми я работаю подобным образом. (В любом случае, если objdump не распознает полученный двоичный файл, я заподозрю, что двоичный файл неверен, и что-то пошло не так.) - person mtraceur; 12.02.2020
comment
@Abhishek Спасибо, я пошел дальше и добавил небольшую дополнительную подсказку в раздел подготовки (шаг 0) о проверке, работает ли hexdump -R, а если нет, то использовать альтернативу, например xxd вместо hexdump для всех команд. - person mtraceur; 12.02.2020

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

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

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

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

person ilpelle    schedule 10.02.2016

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

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

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

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

person Cine    schedule 30.11.2010

миазм

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

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

  • Открытие/изменение/генерация PE/ELF 32/64 LE/BE с помощью Elffeat.
  • Сборка/разборка 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 , определить этот график непросто, см. также: Расчет пункта назначения косвенного перехода

person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 06.12.2017

Мой «дизассемблер ассемблера ci» — единственная известная мне система, разработанная на основе принципа, что чем бы ни была дизассемблерная сборка, она должна собираться в один и тот же двоичный файл байт за байтом.

https://github.com/albertvanderhorst/ciasdis

Приведены два примера elf-исполняемых файлов с их дизассемблированием и повторной сборкой. Изначально он был разработан для возможности модифицировать систему загрузки, состоящую из кода, интерпретируемого кода, данных и графических символов, с такими тонкостями, как переход из реального режима в защищенный. (Удалось.) В примерах также показано извлечение текста из исполняемых файлов, который впоследствии используется для меток. Пакет debian предназначен для Intel Pentium, но доступны плагины для Dec Alpha, 6809, 8086 и т. д.

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

Никаких предположений о двоичном блобе не делается, но, конечно, дизассемблирование Intel мало что дает для двоичного файла Dec Alpha.

person Albert van der Horst    schedule 24.11.2018

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

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

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

person Grzegorz Wierzowiecki    schedule 02.01.2011

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

person user502515    schedule 02.01.2011