Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
metasm — This is the main repository for metasm, a free assembler / disassembler / compiler written in ruby | Kitploit
Инструменты/GitHubGitHub/jjyg/metasm
Code AnalysisReverse EngineeringShellcodeDebuggersBinary Analysis
GitHubjjyg/metasm

metasm

This is the main repository for metasm, a free assembler / disassembler / compiler written in ruby

Репозиторий
4758215 месяцев назадПроверено Kitploit

Популярное

Смотреть все →

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

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Сайт

Metasm, набор средств для работы с ассемблером на Ruby

  • примеры скриптов в samples/ -- читайте комментарии в начале файлов
  • все файлы распространяются на условиях LGPL

Автор: Yoann Guillot

Базовый обзор:

Metasm позволяет вам взаимодействовать с форматами исполняемых файлов (ExeFormat): PE, ELF, Mach-O, Shellcode и т.д. Существует три подхода к ExeFormat:

  • компиляция с нуля
  • декомпиляция существующего формата
  • манипуляция структурой файла

Готовые к использованию скрипты можно найти в подкаталоге samples/, смотрите комментарии в начале скриптов. Можете также попробовать аргумент --help, если чувствуете себя удачливым.

За дополнительной информацией обращайтесь в подкаталог doc/. Текстовые файлы можно скомпилировать в html с помощью скрипта misc/txt2html.rb.

Здесь представлен краткий обзор внутреннего устройства Metasm.

Ассемблирование:

При компиляции вы начинаете с исходного текста (ruby String, в основном состоящего из последовательности инструкций/данных/директив выравнивания), который разбирается.

Строка передаётся экземпляру Preprocessor (который обрабатывает #if, #ifdef, #include, #define, /* */ и т.д. и должен быть на 100% совместим с gcc -E), который для ассемблерных исходников инкапсулируется в AsmPreprocessor (для обработки макроопределений asm, 'equ' и ассемблерных комментариев ';'). Интерфейсом для этого служат ExeFormat#parse(text[, filename, lineno]) или ExeFormat.assemble (который вызывает .new, #parse и #assemble).

(Asm)Preprocessor возвращает токены в ExeFormat, который разбирает их как Data, Padding, Labels или директивы парсера. Директивы парсера всегда начинаются с точки. Они могут быть универсальными (.pad, .offset...) или специфичными для ExeFormat (.section, .import, .entrypoint...). Они обрабатываются методом #parse_parser_instruction(). Если ExeFormat не распознаёт слово, оно передаётся его экземпляру CPU, который отвечает за разбор инструкций (или выбрасывает исключение). Все эти токены хранятся в одном или нескольких массивах в атрибуте @source объекта ExeFormat (@source у Shellcode — это Array, а для PE/ELF это хэш [имя секции] => [Array разобранных данных]). Каждое непосредственное значение может быть произвольным Expression (см. далее).

Затем вы можете собрать исходник в бинарные секции с помощью ExeFormat#assemble.

Когда бинарные секции готовы, весь исполняемый файл можно записать на диск с помощью ExeFormat#encode_file(filename[, format]).

PE и ELF включают функцию автоимпорта, которая позволяет автоматически создавать данные, связанные с импортом, для известных специфичных для ОС функций (например, неразрешённые вызовы 'strcpy' приведут к генерации данных, благодаря которым бинарный файл будет слинкован с библиотекой libc во время выполнения).

Скрипты samples/{exe,pe,elf}encode.rb могут принимать asm-файл в качестве аргумента и компилировать его в работающий исполняемый файл.

Классы CPU отвечают за разбор и кодирование отдельных инструкций. Текущий парсер Ia32 использует синтаксис Intel (например, mov eax, 42). Универсальный парсер распознаёт метки как строку в начале строки, за которой идёт двоеточие (например, 'some_label:'). Можно использовать локальные метки в стиле GCC (например, '1:', на которые ссылаются как '1b' (назад) или '1f' (вперёд); их можно переопределять сколько угодно раз). Данные задаются в нотации в стиле 'db' (например, 'dd 42h', 'db "blabla", 0') См. samples/asmsyntax.rb

EncodedData:

В Metasm все бинарные данные хранятся в виде EncodedData. EncodedData имеет 3 основных атрибута:

  • #data, который содержит необработанные бинарные данные (обычно ruby String, но см. VirtualString)
  • #export, который представляет собой хэш, связывающий имя экспорта (имя метки) со смещением внутри #data
  • #reloc, который представляет собой хэш, ключи которого — смещения внутри #data, а значения — объекты Relocation. Объект Relocation имеет порядок байтов (:little/:big), тип (:u32 для беззнакового 32-битного значения) и цель (предполагаемое значение, которое здесь хранится). Цель — произвольное арифметическое/логическое Expression.

EncodedData также имеет #virtsize (например, для секций .bss) и #ptr (внутреннее смещение, используемое при декодировании).

Вы можете выполнить fixup EncodedData с помощью хэша имя переменной => значение (значение должно быть Expression или числовым значением). При этом цель каждого объекта Relocation связывается с помощью биндинга, и если результат вычислим (в Expression не используется ни одна внешняя переменная), результат кодируется с использованием информации о размере/знаке/порядке байтов объекта Relocation. Если происходит переполнение (например, попытка сохранить 128 в 8-битном знаковом Relocation), выбрасывается исключение EncodeError. Используйте тип :a32, чтобы разрешить молчаливое усечение при переполнении. Если цель Relocation не является числовой, при использовании EncodedData#fixup она остаётся неизменной, а при использовании #fixup! она заменяется на связанную цель.

Дизассемблирование:

Этот код находится в исходном файле metasm/decode.rb, в котором определён класс Disassembler.

Дисассемблеру нужны декодированный ExeFormat (чтобы можно было определить, какие данные находятся по какому виртуальному адресу) и точка входа (виртуальный адрес или имя экспорта). Затем он может начать дизассемблировать инструкции. Когда он встречает опкод, помеченный как :setip, он запрашивает у CPU адрес перехода (Expression, которое может включать значения регистров, например для jmp eax), и выполняет обратную трассировку инструкций, пока не найдёт числовое значение.

В процессе декодирования Disassembler поддерживает хэш #decoded, связывающий адреса (выражения/целые числа, нормализованные с помощью #normalize()) с объектами DecodedInstructions.

В результате дизассемблирования создаётся граф InstructionBlock. Каждый блок содержит список DecodedInstruction и указатели на следующий/предыдущий блок (по адресу).

Дисассемблер также отслеживает обращения инструкций к данным и сохраняет для них перекрёстные ссылки (Xrefs). Параметры обратной трассировки можно настраивать, а максимальную учитываемую глубину можно отдельно изменять для трассировок :r/:w (перекрёстных ссылок инструкций на память) с помощью #backtrace_maxblocks_data. При обратной трассировке Expression каждый пройденный блок помечается, чтобы обнаруживались циклы и чтобы при обнаружении нового пути кода к существующему блоку трассировку можно было возобновить по этому новому пути.

Дисассемблер делает очень мало допущений и, в частности, не предполагает, что функции возвращают управление; они считаются возвращающимися, только если обратная трассировка инструкций 'ret' даёт однозначный результат. Это довольно мощно, но также означает, что любая ошибка в процессе обратной трассировки может привести к полной остановке; кроме того, дисассемблер работает довольно медленно.

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

Когда обнаруживается подфункция, создаётся специальный объект DecodedFunction, содержащий сводку эффектов функции (как DecodedInstruction «на стероидах»). Это позволяет трассировщику «перешагивать» через подфункции, что значительно повышает скорость. DecodedFunctions могут быть основаны на обратных вызовах, что обеспечивает очень динамичное поведение. Вызовы внешних функций создают отдельные объекты DecodedFunctions, которые содержат некоторую информацию об API (например, информацию о коррекции стека, базовые обращения к параметрам...). Эта информация может быть получена из предварительно разобранного C-заголовка. Если прототип C-функции недоступен, используется специальная запись 'default', предполагающая, что функция имеет стандартный ABI.

Ia32 реализует специальную запись :default, которая автоматически разрешает коррекцию стека, предполагая, что последняя инструкция 'call' возвращает управление. Это может привести к неожиданным результатам; для максимальной точности рекомендуется C-заголовок с информацией обо всех внешних функциях (см. samples/factorize-headers-peimports — скрипт для генерации такого заголовка из полной установки Visual Studio и целевого бинарного файла).

Ia32 также реализует специальный обратный вызов GetProcAddress/dlsym, который вернёт корректное значение, если параметры можно проследить обратной трассировкой.

Скрипты, реализующие полноценный дисассемблер, находятся в samples/disassemble{-gui}.rb Смотрите комментарии о привязках клавиш GUI.

Манипуляции с ExeFormat:

Вы можете кодировать и декодировать ExeFormat (то есть декодировать секции, импорты, заголовки и т.д.)

Конструктор: ExeFormat.decode_file(str), ExeFormat.decode_file_header(str) Методы: ExeFormat#encode_file(filename), ExeFormat#encode_string

У файлов PE и ELF есть аналоги LoadedPE/LoadedELF, которые умеют работать с отображаемыми в память (memory-mmap) версиями этих форматов (например, для отладки запущенных процессов)

VirtualString:

VirtualString — это объект, подобный String: вы можете читать и перезаписывать его срезы. Его можно использовать как EncodedData#data, что позволяет виртуализировать большинство алгоритмов Metasm. Вы не можете изменить длину VirtualString. Взятие среза VirtualString вернёт либо String (для небольших размеров), либо другой VirtualString («окно» в другой). Метод #dup(offset, length) позволяет принудительно получить небольшой VirtualString. Любой нереализованный метод, вызванный на нём, перенаправляется замороженной (frozen) String, представляющей полную копию VirtualString (этого следует по возможности избегать: нижележащая строка может быть очень большой, и доступ к ней медленный).

В настоящее время реализованы 3 VirtualStrings:

  • VirtualFile, который загружает файл по требованию порциями размером со страницу,
  • WindowsRemoteString, который отображает виртуальную память другого процесса (использует отладочный API Windows через WinDbgAPI)
  • LinuxRemoteString, который отображает виртуальную память другого процесса (требуются права ptrace, чтение памяти выполняется через /proc/pid/mem)

Версии для Win/Lin довольно мощные и позволяют легко выполнять дизассемблирование/патчинг живых процессов (используя LoadedPE/LoadedELF в качестве ExeFormat)

Отладка:

Metasm включает несколько интерфейсов для отладки. Классы WinOS и LinOS предоставляют доступ к процессам базовой ОС (например, OS.current.find_process('foobar') найдёт запущенный процесс с foobar в имени файла; затем для доступа к его памяти можно использовать process.mem).

Низкоуровневые отладочные API Windows и Linux имеют базовый интерфейс на ruby (PTrace и WinAPI); они используются унифицированным высокоуровневым классом Debugger. Удалённая отладка поддерживается через сетевой протокол GDB server.

Высокоуровневые отладчики можно создать следующей строкой на ruby: Metasm::OS.current.create_debugger('foo')

Одновременно может существовать только один вид класса хост-отладчика; для отладки нескольких процессов подключайтесь к другим процессам с помощью существующего класса. Это связано с особенностями работы отладочного API ОС в Windows и Linux.

Низкоуровневые бэкенды определены в подкаталоге os/, а фронтенд — в debug.rb.

Консольный отладочный интерфейс для Linux доступен в samples/lindebug.rb; он имеет (упрощённый) внешний вид и поведение в стиле SoftICE. Он может общаться с сокетом gdb-server; используйте цель [udp:]host:port.

Пример disassembler-gui позволяет взаимодействовать с живым процессом, если использовать цель 'live:<pid или часть имени программы>'.

C-парсер:

Metasm включает C-парсер, написанный вручную. Он обрабатывает все известные мне конструкции, кроме шестнадцатеричных чисел с плавающей запятой (hex floats):

  • static const L"bla"
  • аргументы переменной длины
  • неполные типы
  • attributes(()), __declspec()
  • #pragma once
  • #pragma pack()
  • деклараторы C99 - type bla = { [ 2 ... 14 ].toto = 28 };
  • вложенные функции
  • __int8 и другие нативные типы
  • адреса меток (&&label) Также обратите внимание: все эти конструкции разбираются, но большинство из них не скомпилируется на бэкенде Ia32/X64 (единственном реализованном на данный момент).

Разбор C-файлов следует выполнять с использованием существующего ExeFormat и метода parse_c_file. Это гарантирует, что специфичные для формата макросы/ABI определены корректно (например, размер типа 'long', ABI для передачи параметров функциям и т.д.).

При разборе C-строки с помощью C::Parser.parse(text) вы получаете объект Parser. Он содержит поле #toplevel, представляющее собой C::Block, в котором хранятся #structs, #symbols и #statements. Функции верхнего уровня находятся в хэше #symbol, ключи которого — имена символов, сопоставленные объекту C::Variable, содержащему функции. Параметры/атрибуты функции доступны через func.type, а код — в func.initializer, который сам является C::Block. Внутри вы найдёте древовидную структуру из C::Statements (If, While, Asm, CExpressions...).

C::Parser можно преобразовать методом #precompiled в упрощённую версию, которую легче компилировать: typedef удаляются, управляющие последовательности превращаются в 'if (XX) goto YY;' и т.д.

Для компиляции C-программы используйте PE/ELF.compile_c — она создаст C::Parser со специфичными для исполняемых файлов макросами (например, PE или ELF).

Для вендорских заголовков может потребоваться использовать либо #pragma prepare_visualstudio (для разбора заголовков Microsoft Visual Studio), либо prepare_gcc (для gcc); последний может определяться автоматически (а может и нет). Протестированные вендорские заголовки: VS2003 (включая DDK) и gcc4; результат может быть иным (ymmv).

В настоящее время компиляция C-кода через CPU#compilation генерирует asm-исходник (текст), который затем можно разобрать и ассемблировать в бинарный код.

См. ExeFormat#compile_c и samples/exeencode.rb.

Скачать инструмент