Набор статей и руководств по дизассемблеру IDA

Как IDA дизассемблирует программы


Чтобы понять, для чего именно предназначена моя библиотека, Вы должны очень хорошо представлять себе, как же IDA работает. Так что запаситесь терпением, пивом, поставьте что-нть приятное слуху (от себя могу порекомендовать Marylin Manson, Nine Inch Nails, Pearl Jam или Alice in Chains) - глава будет долгой...

Я встречал много людей, которые на полном серьёзе утверждали, что создание дизассемблера - тривиальное занятие. Хм, однако почему в таком случае так мало хороших дизассемблеров ? Дело в том, что под понятием "хороший дизассемблер" мы подразумеваем не только программу, генерирующую на выходе текстовый файл с ассемблерными инструкциями. Хороший ассемблер должен заниматься так же и анализом кода - чтобы отличить код от данных, распознать использование локальных переменных, начало и конец функций и ещё множество вещей, за которые мы любим IDA Pro. А как же IDA Pro может делать всё вышеперечисленное ? Весь секрет заключается в том, что в ней используется не просто дизассемблер - а ещё и эмулирующий анализатор.

Чтобы заставить IDA Pro понимать ещё один процессор (скажем, HP-PA), Вы должны написать dissassembler module - для краткости в дальнейшем будем называть его просто module. В IDA SDK есть пример такого модуля (для процессора 8051). Рассмотрим, из каких функций состоит module. Module представляет собой .DLL (для Win32), экспортирующую под именем LPH всего одну структуру processor_t. Структура эта достаточно велика - ведь она должна полностью описывать ассемблер некоторого процессора - но нас в этой структуре нас интересует всего несколько членов:

  • int (*u_ana) (void)

    Указатель на функцию, анализирующую одну инструкцию, в результате анализа заполняется глобальная переменная cmd - структура insn_t. Адрес инструкции задаётся в поле cmd.ea. Функция возвращает длину декодированной инструкции, или 0, если инструкция не распознана.

    На самом деле мы не можем вызвать эту функцию непосредственно - мы можем вызвать для анализа только следующие функции:

    1. ida_export int ua_code(ea_t ea)


      Высокоуровневая функция, анализирует байты по адресу ea, и преобразует их в код.


    2. ida_export int ua_ana(ea_t ea)

      Анализирует байты байты по адресу ea, преобразует их в код, а также производит некоторые сопутствующие действия (например, применение fixups, увеличение сегментов и т.д.)


    3. ida_export int ua_ana0(ea_t ea)

      Наша рабочая лошадка - просто анализирует байты по адресу ea, заполняя структуру cmd, при этом загруженная база данных не изменяется.




    4. Все эти три функции возвращают длину декодированной инструкции, или 0, если инструкция не опознана. Аргумент ea - адрес (ea_t - просто ulong).

    5. int (*u_emu) (void)

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


    6. Функции генерации текстового представления (то, что мы видим на экране):


    • void (*u_out) (void)

      Генерирует текстовое представление инструкции по ранее заполненной структуре cmd.


    • int (*u_outop) (op_t &op)

      Генерирует текстовое представление операнда op_t инструкции. Возвращает 1 в случае успеха, и 0, если операнд скрыт.


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

    • instruc_t *instruc

      Массив описаний инструкций (см. подробности ниже).


    • Структура instruc_t используется для внутреннего представления характеристик инструкции, и имеет всего два члена:


      • const char near *name

        Строка - имя инструкции


      • ushort feature

        Характеристики инструкции. Битовая маска, могущая состоять из следующих значений:




      • CF_STOP

        Инструкция не передаёт исполнение следующей инструкции (например, hlt)


      • CF_CALL

        Вызов процедуры.


      • CF_CHG1

        Инструкция модифицирует свой первый операнд.


      • CF_CHG2

        Инструкция модифицирует свой второй операнд.


      • CF_CHG3

        Инструкция модифицирует свой третий операнд.


      • CF_USE1

        Инструкция использует значение своего первого операнда.


      • CF_USE2

        Инструкция использует значение своего второго операнда.


      • CF_USE3

        Инструкция использует значение своего третьего операнда.


      • CF_JUMP

        Инструкция передаёт управление.


      • CF_SHFT

        Инструкция производит побитовый сдвиг.


      • CF_HLL

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


      • Структура insn_t используется для внутреннего представления инструкции, нас интересуют следующие члены:


        • ushort itype

          Внутренний код инструкции. Значения кодов определены в каждом процессорном модуле по-разному для каждого процессора (я также сильно подозреваю, что их значения изменяются от версии к версии). Также является индексом в ранее описанном массиве инструкций instruc_t *instruc.


        • ea_t ea

          Линейный адрес инструкции.


        • ushort size

          Размер инструкции в байтах.


        • union { ushort auxpref; struct { uchar low; uchar high; } auxpref_chars; };

          Процессорно-зависимое поле. Используется для x86 процессора (см. детальное описание ниже).

        • op_t Operands[3]

          Операнды инструкции. Почему операндов именно три ? См., например, x86 инструкцию shld или shrd


        • Структура op_t заслуживает более пристального рассмотрения. Она состоит из следующих членов:


          • char n

            Номер операнда - 0,1 или 2.


          • optype_t type

            Тип операнда. Этот член определяет, какие прочие члены структуры имеют значение, и как оно интерпретируется. Насколько я понял, процессорозависим. Более детальное описание см. ниже.


          • char offb

            Смещение значения операнда от начала инструкции. Имеет смысл не для всех типов операндов.


          • char offo

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




          • uchar flags

            Некоторые характеристики операндов. Объяснение см. ниже.


          • char dtyp

            Тип значения операнда. Наиболее распространённые типы значений:

            dt_byte 0 // 8 бит dt_word 1 // 16 бит dt_dword 2 // 32 бит dt_float 3 // 4 байта dt_double 4 // 8 байт dt_qword 7 // 64 бит

            Все нижеописанные члены структуры op_t хранят информацию о значении операнда (и их использование зависит от значений type & flags):


              union { uchar reg; uchar phrase; };

              Номер регистра. Для ассемблера x86 номера регистров определены в файле заголовков x86.h моей библиотеки - см. макросы r_XX.

              union { ulong value; struct { ushort low; ushort high; } value_shorts; };

              Значение непосредственного операнда.

              union { ulong addr; struct { ushort low; ushort high; } addr_shorts; };

              Значение виртуального адреса, используемого операндом.

              union { ulong specval; struct { ushort low; ushort high; } specval_shorts; };

              Содержит значение операнда, не могущее быть представленным остальными членами структуры op_t. Специфично для каждого процессорного модуля, для ассемблера x86 не используется (IMHO ?).

              char specflag1; char specflag2;

              Содержат дополнительную информацию об операнде.

              Как же именно вышеописанные структуры могут быть использованы для анализа кода ? Я выяснил этот вопрос, не дождавшись ответа от Гильфанова. Хочу ещё раз предупредить, что всё нижеописанное касается только IDA Pro версии 3.85b (и может измениться без предупреждения в любой из следующих версий) и только процессора x86.

              Итак, инструкция может иметь один из префиксов. Наличие или отсутствие префиксов определяет поле auxpref_chars.low структуры insn_t:


              • lock auxpref_chars.low & 0x1 == 0x1


              • rep/repe auxpref_chars.low & 0xE == 0xA


              • repne auxpref_chars.low & 0xE == 0xC


              • Количество операндов можно определить так: последний из них должен иметь type

                o_void (но не больше трёх).

                Значение операнда определяется его полем type:


                • o_void

                  Операнд не имеет значение. Признак отсутствия операнда и всех последующих операндов.




                • o_reg

                  Операнд является регистром, поле reg содержит номер регистра. Значения регистров см. в моём файле заголовков x86.h.


                • o_mem

                  Операнд является прямой ссылкой на данные в памяти. Сегментный регистр содержится в поле specval_shorts.high, поле addr содержит адрес.


                • o_phrase

                  Операнд представляет собой ссылку типа [базовый регистр + индексный регистр]. Сегментный регистр содержится в поле specval_shorts.high. Все значения операнда хранятся в SIB байте - поле specflag2.

                  Значение базового регистра определяется как specflag2 & 0x7

                  Значение индексного регистра определяется как (specflag2 >> 3) & 0x7

                  Масштабный индекс определяется как (specflag2 & 0xC0) >> 6:

                  0 eq 0

                  1 eq 2

                  2 eq 4

                  3 eq 8

                  По этой схеме specflag2, равный, скажем, 64, представляет операнд [eax+eax*2]


                • o_displ

                  Операнд представляет собой ссылку типа [базовый регистр + индексный регистр + смещение]. Сегментный регистр содержится в поле specval_shorts.high. Поле phrase содержит значение базового регистра. Также используется SIB байт (см. описание выше). Поле value содержит значение, если flags & OF_OUTER_DISP.

                  Поле addr содержит адрес, если !(flags & OF_NO_BASE_DISP).


                • o_imm

                  Операнд содержит непосредственное значение в поле value.


                • o_far & o_near

                  Операнд содержит дальнюю и ближнюю ссылку на адрес в поле addr. Сегментный регистр содержится в поле specval_shorts.high.


                • 11

                  Для инструкций с плавающей точкой - поле reg содержит номер регистра FPU (0 - ST(0), 1 - ST(1) и т.д.)


                • 12

                  Для инструкций с использованием MMX - поле reg содержит номер регистра MMX (0 - MM0, 1 - MM1 и т.д.)


                • 13

                  Для инструкций с использованием SSE - поле reg содержит номер регистра SSE (0 - XMM0, 1 - XMM1 и т.д.)



                • Содержание раздела