Хотя ни одна из представленных здесь сведений не требует, чтобы вы прочитали первую часть, рекомендуется прочитать ее здесь. Эта часть описывает файловую структуру, в этой части рассказывается, как понять, как она работает. (Быстрый отказ от ответственности, большая часть этой части изначально была сделана не мной, а замечательными умами @SammiHusky и dantarion (@dantarion on twitter), но все мысли здесь являются моими собственными. Хотя мне не нужно было выполнять эту часть обратной инженерии с нуля, мне все же пришлось проследить ее, чтобы понять последние вещи, которые я сделал с этими знаниями.)

Начиная

Практически со всем, что связано с обратным проектированием, сложнее всего понять, как вы вначале начинаете понимать. На самом деле в этом нет никакого трюка, вам просто нужно искать выход. Иногда вам нечего делать, и вам просто нужно пробовать теорию, пока она не сработает. Для байт-кода вы должны помнить, что, как и в любом языке байт-кода / ассемблера, будут шаблоны, потому что в коде есть шаблоны. Что-то, что вы можете в разумной степени угадать и сделать это правильно. В этом случае мы действительно знаем, что есть большая вероятность, что это переменный размер, поскольку смещения скрипта не имеют никакого шаблона выравнивания (например, в байт-коде powerpc вы знаете, что каждая инструкция будет со смещением, кратным 4, поскольку каждая инструкция 32 бита. Теперь посмотрим на шаблоны в байт-коде; Если мы посмотрим на какой-то код Python, не понимая, что он вообще делает

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

Начало и конец

Так что здесь стоит отметить пару моментов. Во-первых, как я очень надеюсь, вы заметили, первый байт всегда 02. Во-вторых, предшествующий байт всегда 03 (кроме первого скрипта), а последний байт последнего скрипта - 03. Это не слишком трудно соединить точки и выяснить, что 02 - это команда, используемая в начале каждого скрипта, а 03 используется в самом конце каждого скрипта. Еще одна полезная вещь, которую следует отметить, заключается в том, что за большинством байтов 02 следуют 4 нулевых байта, некоторые из них - это 3 нулевых байта и некоторые другие числа, а пара других имеет (нулевой) номер (нулевой) номер. Что мы можем из этого получить? Мы можем с достаточной уверенностью предположить, что, вероятно, два коротких байта (2-байтовые целые числа) имеют обратный порядок байтов по следующим причинам:

  1. Первый байт всегда равен 0, это заставляет меня думать, что это самый значимый байт.
  2. Третий байт никогда не может быть ничем иным, кроме нуля, но второй байт может быть чем-то другим, кроме нуля.

Вот почему я считаю, что это два коротких слова, а не полное int или четыре отдельных байта или даже отдельные команды. Что касается 03, это не только имеет смысл для команды, добавленной после начала, являющейся концом, но также имеет смысл, чтобы конец не имел дополнительных параметров. Думайте об этом как о коде C:

У нас есть

void* getAddressRelative(void* address, u32 length){

Которая содержит много информации в начале функции, а в конце у нас есть

}

Которая практически не содержит информации, это просто означает конец. И хотя байт-код и код C, безусловно, не очень сопоставимы, это предположение безопасно, потому что мы знаем, что каждый сценарий заканчивается им. Итак, мы знаем, как запускается сценарий (даже не аргументы) и чем он заканчивается, как это вообще может помочь? Что ж, поскольку у нас есть хорошее представление о длине команды begin, это означает, что мы можем пробовать некоторые другие команды, так как мы знаем, что другая команда должна следовать сразу после. Если я изменю свой шаблон редактора 010 (действительно полезный инструмент для этого, у меня есть репозиторий с некоторыми примерами, также взятыми из smash на моем github здесь), чтобы выделить шестой байт после каждого смещения скрипта. Если вам интересно, как это выглядит: Я загрузил это для тех, кто хочет попробовать сами.

Битфилд

Результат выглядит примерно так:

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

  • 8A — 10001010
  • 8D — 10001101

Вы можете распознать это так, что 8 - это степень двойки, часто, если вы видите значение, в котором многие значения кажутся степенью двойки, вам следует проверить битовые поля или, по крайней мере, рассмотреть вариант. Битовое поле (для тех, кто не знает) - это когда вместо использования всего байта как 0 или 1 у вас есть несколько логических значений (истина / ложь), хранящихся в одном байте, причем каждый бит занимает поле. В приведенном выше примере первый бит, вероятно, является битовым полем, потому что мы видим его как единственный бит в верхней половине байта, который используется во многих этих примерах. С обычными прогрессирующими числами вы обычно этого не видите, потому что вам нужно пройти через нижние 128 значений, прежде чем этот бит будет использован. Поскольку мы не видим, что 2-й, 3-й и 4-й биты часто используются, мы можем предположить, что это битовое поле ... но для чего? Мы знаем, что это модификатор для самой команды, поскольку он сгруппирован с командой. Но поскольку у нас не так много информации, нам просто нужно держать ее в памяти. Если мы более подробно рассмотрим некоторые из этих команд, начиная с 8, мы обнаружим, что за 8A следует 4-байтовое int, а за 8D - 2-байтовое int. Чтобы найти это, вы используете те же методы, которые упоминались ранее, чтобы посмотреть количество встречающихся 00, в каком порядке байтов он находится (прямой / обратный порядок байтов), а также немного предположить и проверить, что совпадает. Мы даже можем видеть 8A внизу скриншота, опубликованного выше, с 8A FF FF FF FF, который, кажется, очень хорошо соответствует этому, так что мы, вероятно, сможем продолжить, не беспокоясь. Если мы посмотрим на некоторые другие выделенные байты, мы обнаружим, что - о нет - они не начинаются с 8.

но если мы снова разберем его на двоичный, мы обнаружим, что это тот же тип сделки, но на самом деле с использованием некоторых верхних битов. В этом случае у нас есть 10101110, и сейчас трудно сказать, является ли это другой частью битового поля или частью номера команды. Единственным «реальным» доказательством того, что это еще одно битовое поле, является другой выделенный байт 2E, который либо означает, что бит может быть полностью отделен от самого старшего бита, либо является частью числа. Если посмотреть на все команды с этим MSB (как теперь я буду сокращать старший бит), все они имеют число после них. Это может означать, что что-то делается с ценностью, но мы не можем быть полностью уверены. Однако остальная часть этого процесса мало что может с этим сделать, мы можем предположить, какие байты являются командными байтами, а какие - параметрами для этих команд, и мы можем попытаться получить простую, немаркированную работу по дизассемблированию. Это позволяет нам видеть частоту команд, шаблоны того, какая команда перед какой и т. Д. Этот процесс довольно утомительный и по сути такой же, как начало / конец, просто посмотрите вокруг и попробуйте найти шаблоны, чтобы определить размер параметры. Хотя это оставляет вас с разборкой, которая выглядит как

    begin 0x0, 0x0
->  unk_2E 0x24A
->  unk_A 0x0
    end

В нем можно найти тонкие узоры. Такие вещи, как набор команд, используются только в том случае, если второй аргумент begin больше 0 (в этом случае второй аргумент - это счетчик локальной переменной) или что-то вроде первого параметра begin только больше 0, если скрипт вернул значение и т. д. Хотя это непростая часть, Сэмми Хаски и Дантарион пришлось много биться головой об стену, чтобы не документировать даже половину команд, и они более опытны, чем я. Я провел большой первоначальный анализ этого байт-кода, когда начал вносить свой вклад, и мои методы стали немного другими после того, как через некоторое время возникли похожие проблемы. Такие вещи, как думали, что у begin было 4 однобайтовых параметра, были проблемой, и это позже пришлось исправить. Это немного итеративный процесс, и для более простых байт-кодов это намного проще. Однако ответы на некоторые из более сложных инструкций пришлось искать в другом месте.

Вывод

Если вы хотите узнать больше о работах MSC, прочтите мой wiki-учебник по моему репозиторию pymsc. Если вы хотите узнать о моих методах определения оставшейся половины системных вызовов, подпишитесь на меня здесь в среде или в твиттере. Так что вы не пропустите, когда я его опубликую. Надеюсь, он должен быть более подробным, чем этот шаг, поскольку на этом шаге я действительно чувствовал себя комфортно только с частями, которые мне лично пришлось переработать, чтобы выяснить проблемы, которые у меня были с интерпретацией Сэмми / Дантом MSC. Если у вас есть какие-либо вопросы / комментарии / проблемы, не стесняйтесь комментировать или писать мне в твиттере, и я ценю любые отзывы, будь то исправления или любые другие формы взаимодействия.