Какая полная информация необходима для клонирования инструкции по сборке - PullRequest
2 голосов
/ 13 февраля 2012

Я хочу создать код, который читает инструкции по сборке (только для x86) и воссоздает их в другом месте памяти для создания кода подключения. Например, я хочу перехватить функцию X, поэтому мне нужно пропатчить ее (как минимум) первые байты с помощью перехода и каждую заменяемую инструкцию (что может варьироваться в зависимости от кода сборки) (частично или нет) мне нужно воссоздать в памяти мой блок, а затем добавить инструкцию, чтобы вернуться к исходной функции X со смещением следующей инструкции, которую я не трогал. Вы, наверное, знаете, что я говорю, так как это не ново для многих. Я не хочу делать полную идеальную программу, но я хочу создать полностью расширяемую кодовую базу, которая будет использовать дерево, как я объясню ниже. Для начала давайте представим несколько инструкций:

  • A - «0x12 0x13.» Эта инструкция имеет 4 байта, а первые два являются статическими.
  • B - "0x12." Эта инструкция имеет 2 байта, а первый является статическим.

Для этого случая у меня будет дерево, которое будет выглядеть как

    Tree
     |
     |
    0x12
   /  \
  B   0x13
        |
        A

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

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

Итак, теперь мой вопрос: какова точная полная информация, которая понадобится dll, которая будет обрабатывать инструкцию кода? Нравится:

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

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

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

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

Итак, что-то вроде

void* copy_instructions(void* address,int& len)
{
    int bytes_copied = 0;
    void* instructions = block of bytes // don't care about the implementation

    do
    { 
        void (*copy_instruction)(void*,int*) = get_a_handler_to_instruction_at(address) // this function will use the tree structure and retrieve a function from a dll

        if(copy_instruction != NULL)

            int len = 0;
            void* instruction = copy_instruction(void* address,&len,...) // I want to know how to make this function complete in terms of what it need for every case

            if(!instruction)
                fail

            instructions += instruction // don't care about the implementation

            address += len
            bytes_copied += len
        else
                fail
    }
    while(bytes_copied < 5)

    add_instructions_jump_to(instructions,address + bytes_copied)

    len = bytes_copied;

    return 
}

Мои вопросы будут:

Как будет выглядеть полный заголовок функции copy_instruction? Можно ли использовать дерево, указанное выше, для реализации "get_a_handler_to_instruction_at" или мне нужно что-то еще?

1 Ответ

3 голосов
/ 13 февраля 2012

Чтобы подключить функцию, вам необходимо:

  1. Знать ее интерфейс (соглашение о вызовах, номер параметра и типы, все это).Компилятор может встроить функцию или дурачиться в интерфейсе при оптимизации кода.Если это так, я не знаю, как лучше всего справиться с этим.Вам может понадобиться настроить код так, чтобы функция вызывалась через изменчивый указатель на функцию, пытаясь убедить компилятор, что указатель может изменить свое значение в любое время и указать на любую другую функцию с такими же параметрами, и это было бы неразумноизменить пролог и эпилог функции.Отключение оптимизации может быть другим вариантом.Все это необходимо, чтобы избежать ситуации, когда исходная и новая функции несовместимы с точки зрения того, как они получают параметры и возвращают их.Однако, если это одна из экспортируемых функций, компилятор, очевидно, ничего не изменит, поскольку нарушит код.
  2. Знайте свой адрес.
  3. Минимально разберите первые инструкции функции,которую вы собираетесь перезаписать с помощью инструкции перехода к новому коду.При разборке вы должны выяснить: длину инструкции (для этого вам необходимо правильно проанализировать все префиксы команд, все байты кода операции, все байты Mod / Rm / SIB, все байты смещения и все байты непосредственного операнда; некоторая логика + поисктаблицы), будет ли эта инструкция передавать управление или получать доступ к данным в местоположении относительно указателя инструкции (например, Jcc, JMP near, CALL near, JMP/CALL qword ptr [RIP+something], MOV EAX, dword ptr [RIP+something]) и, если это так, целевой адрес.
  4. Знайте адрес копий оригинальной инструкции.В идеале вы должны выделить память для копий после разбора инструкций, но вы можете (и, вероятно, должны) выделить еще больше, чтобы упростить свою жизнь.
  5. Скопируйте оригинальные инструкции на новое место и при необходимости отрегулируйте относительныеОбращайтесь к ним по разнице между старым и новым расположением этих инструкций.Обратите внимание, что в исходных инструкциях могут использоваться очень короткие относительные адреса (например, 8-разрядный (наиболее распространенный случай для Jcc) или даже 16-разрядный), которые недостаточно коротки для простого прямого исправления.В этом случае вам потребуется пересобрать такие инструкции с более длинными относительными адресами (для этого потребуется либо вставить / изменить префикс инструкции, либо изменить байты Mod / RM / SIB).Имейте в виду, что относительные адреса относятся к концу инструкции (или, IOW, началу следующей инструкции), что означает, что если скорректированная инструкция длиннее оригинала, относительный адрес должен будет учитывать разницу длины инструкции какЧто ж.В идеале, вы также должны быть готовы обработать случай, когда оригинальные инструкции, которые вы перезаписываете, jmp друг другу.Вы не хотите, чтобы их копии возвращались к перезаписанному коду.
  6. Добавьте инструкцию JMP, которая переходит к первой нетронутой (перезаписываемой) инструкции в исходной функции.

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

Что касается структуры данных, вы заменяете N байтов исходного кода.N - это 5 для 32-битного прыжка.Эти N байтов будут соответствовать не более N оригинальным инструкциям.Вам нужно будет сохранить эти инструкции от 1 до N во всей их полноте (каждая инструкция имеет длину не более 15 байт, IIRC), затем проанализировать, возможно откорректировать и сохранить в новом месте.Вам не нужно дерево здесь, достаточно массива.Элемент в инструкции.Просто.Но это некоторый код, который нужно тщательно написать и отладить / протестировать.

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

РЕДАКТИРОВАТЬ: Ответ на главный вопрос:

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

...