Модернизация IDA Pro. Исправляем косяки процессорных модулей

от автора

Привет всем,

Спустя довольно таки продолжительное время с момента написания первой статьи я всё таки решил, пусть и по чуть-чуть, но писать статьи на тему модификации/улучшения всеми любимой и одновременно презираемой IDA Pro.

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

Локализуем баги

Примечание: здесь и далее будут рассматриваться ошибки в модуле Motorola M68000 (моём самом любимом и очень часто используемом).

Итак, первый косяк: адресация относительно регистра PC. Ошибка заключается в том, что дизассемблерный листинг для таких инструкций не всегда корректен. Взглянем на скриншот:

Кажется, что ошибки здесь нет. Более того, её наличие не мешает анализу. Но, опкод дизассемблирован неправильно. Посмотрим на дизазм в каком-нибудь онлайн-дизассемблере:

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

Косяк два: «зеркала» для оперативной памяти, и некоторых других регионов. Т.к. адресация в m68k 24-битная, то все обращения к старшим (или наоборот, младшим) регионам, должны быть переадресованы на один и тот же диапазон, как и перекрёстные ссылки.

Косяк три (скорее, даже не косяк, а отсутствие функционала): так называемые lineA (1010) и lineF (1111) эмуляторы. Это такие опкоды, на которые не хватило основного набора команд, поэтому они должны обрабатываться специальным образом векторами прерываний. Размер опкодов зависит лишь от реализации на уровне обработчика. Я видел лишь двухбайтовую реализацию. Будем добавлять.

Косяк четыре: trap #N инструкции не дают cref-ов на сами обработчики «ловушек».

Косяк пять: movea.w инструкция должна делать из word-ссылки полный xref на адрес, но мы имеем только word-число.

Исправляем баги (шаблон)

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

Собственно, «исправлялка» представляет из себя обычный плагин. Его, вроде как, можно написать и на Python, но, я всё делал в «плюсах». Страдает лишь портабельность, но, если кто-то возьмётся переписать плагин на Python — буду очень благодарен.

Для начала создадим пустой DLL-проект в Visual Studio: File->New->Project->Windows Desktop Wizard->Dynamic link library (.dll), поставив также галку Empty Project, и сняв все остальные:

Распакуем IDA SDK, и пропишем его в макросах Visual Studio (я буду использовать 2017), чтобы в будущем можно было легко ссылаться на него. Заодно мы добавим макрос для пути к IDA Pro.

Заходим в View->Other Windows->Property Manager:

Т.к. мы работаем с версией SDK 7.0, компиляция будет происходить x64-компилятором. Поэтому выбираем Debug | x64->Microsoft.Cpp.x64.user->Properties:

Жмём кнопку Add Macro в разделе User Macros, и прописываем там макрос IDA_SDK с указанием пути, по которому вы распаковали SDK:

Так же поступаем с IDA_DIR (путь к Вашей IDA Pro):

Замечу, что IDA ставится по умолчанию в %Program Files%, что требует прав администратора.

Давайте также удалим Win32 конфигурацию (в данной статье я не буду затрагивать компиляцию по x86 системы), оставив только x64-вариант.

Создадим пустой файл ida_plugin.cpp. Код пока не добавляем.
Теперь появилась возможность выставить кодировку, и другие настройки для C++:

Пропишем инклуды:

И библиотеки из SDK:

Теперь добавим шаблон кода:

Код ida_plugin.cpp

#include <ida.hpp> #include <idp.hpp> #include <ua.hpp> #include <bytes.hpp> #include <loader.hpp> #include <offset.hpp>  #define NAME "M68000 proc-fixer plugin" #define VERSION "1.0"  static bool plugin_inited; static bool my_dbg;  //-------------------------------------------------------------------------- static void print_version() {     static const char format[] = NAME " v%s\n";     info(format, VERSION);     msg(format, VERSION); }  //-------------------------------------------------------------------------- static bool init_plugin(void) {     if (ph.id != PLFM_68K)         return false;      return true; }  #ifdef _DEBUG static const char* const optype_names[] = {     "o_void",     "o_reg",     "o_mem",     "o_phrase",     "o_displ",     "o_imm",     "o_far",     "o_near",     "o_idpspec0",     "o_idpspec1",     "o_idpspec2",     "o_idpspec3",     "o_idpspec4",     "o_idpspec5", };  static const char* const dtyp_names[] = {     "dt_byte",     "dt_word",     "dt_dword",     "dt_float",     "dt_double",     "dt_tbyte",     "dt_packreal",     "dt_qword",     "dt_byte16",     "dt_code",     "dt_void",     "dt_fword",     "dt_bitfild",     "dt_string",     "dt_unicode",     "dt_3byte",     "dt_ldbl",     "dt_byte32",     "dt_byte64", };  static void print_insn(const insn_t *insn) {     if (my_dbg)     {         msg("cs=%x, ", insn->cs);         msg("ip=%x, ", insn->ip);         msg("ea=%x, ", insn->ea);         msg("itype=%x, ", insn->itype);         msg("size=%x, ", insn->size);         msg("auxpref=%x, ", insn->auxpref);         msg("segpref=%x, ", insn->segpref);         msg("insnpref=%x, ", insn->insnpref);         msg("insnpref=%x, ", insn->insnpref);          msg("flags[");         if (insn->flags & INSN_MACRO)             msg("INSN_MACRO|");         if (insn->flags & INSN_MODMAC)             msg("OF_OUTER_DISP");         msg("]\n");     } }  static void print_op(ea_t ea, const op_t *op) {     if (my_dbg)     {         msg("type[%s], ", optype_names[op->type]);          msg("flags[");         if (op->flags & OF_NO_BASE_DISP)             msg("OF_NO_BASE_DISP|");         if (op->flags & OF_OUTER_DISP)             msg("OF_OUTER_DISP|");         if (op->flags & PACK_FORM_DEF)             msg("PACK_FORM_DEF|");         if (op->flags & OF_NUMBER)             msg("OF_NUMBER|");         if (op->flags & OF_SHOW)             msg("OF_SHOW");         msg("], ");          msg("dtyp[%s], ", dtyp_names[op->dtype]);          if (op->type == o_reg)             msg("reg=%x, ", op->reg);         else if (op->type == o_displ || op->type == o_phrase)             msg("phrase=%x, ", op->phrase);         else             msg("reg_phrase=%x, ", op->phrase);          msg("addr=%x, ", op->addr);          msg("value=%x, ", op->value);          msg("specval=%x, ", op->specval);          msg("specflag1=%x, ", op->specflag1);         msg("specflag2=%x, ", op->specflag2);         msg("specflag3=%x, ", op->specflag3);         msg("specflag4=%x, ", op->specflag4);          msg("refinfo[");          opinfo_t buf;          if (get_opinfo(&buf, ea, op->n, op->flags))         {             msg("target=%x, ", buf.ri.target);             msg("base=%x, ", buf.ri.base);             msg("tdelta=%x, ", buf.ri.tdelta);              msg("flags[");             if (buf.ri.flags & REFINFO_TYPE)                 msg("REFINFO_TYPE|");             if (buf.ri.flags & REFINFO_RVAOFF)                 msg("REFINFO_RVAOFF|");             if (buf.ri.flags & REFINFO_PASTEND)                 msg("REFINFO_PASTEND|");             if (buf.ri.flags & REFINFO_CUSTOM)                 msg("REFINFO_CUSTOM|");             if (buf.ri.flags & REFINFO_NOBASE)                 msg("REFINFO_NOBASE|");             if (buf.ri.flags & REFINFO_SUBTRACT)                 msg("REFINFO_SUBTRACT|");             if (buf.ri.flags & REFINFO_SIGNEDOP)                 msg("REFINFO_SIGNEDOP");             msg("]");         }         msg("]\n");     } } #endif  static bool ana_addr = 0;  static ssize_t idaapi hook_idp(void *user_data, int notification_code, va_list va) {     switch (notification_code)     {     case processor_t::ev_ana_insn:     {         insn_t *out = va_arg(va, insn_t*);          if (ana_addr)             break;          ana_addr = 1;          if (ph.ana_insn(out) <= 0)         {             ana_addr = 0;             break;         }          ana_addr = 0;  #ifdef _DEBUG         print_insn(out); #endif          for (int i = 0; i < UA_MAXOP; ++i)         {             op_t &op = out->ops[i];  #ifdef _DEBUG             print_op(out->ea, &op); #endif         }          return out->size;     } break;     case processor_t::ev_emu_insn:     {         const insn_t *insn = va_arg(va, const insn_t*);     } break;     case processor_t::ev_out_mnem:     {         outctx_t *outbuffer = va_arg(va, outctx_t *);         //outbuffer->out_custom_mnem(mnem);         //return 1;     } break;     default:     { #ifdef _DEBUG         if (my_dbg)         {             msg("msg = %d\n", notification_code);         } #endif     } break;     }     return 0; }  //-------------------------------------------------------------------------- static int idaapi init(void) {     if (init_plugin())     {         plugin_inited = true;         my_dbg = false;          hook_to_notification_point(HT_IDP, hook_idp, NULL);          print_version();         return PLUGIN_KEEP;     }     return PLUGIN_SKIP; }  //-------------------------------------------------------------------------- static void idaapi term(void) {     if (plugin_inited)     {         unhook_from_notification_point(HT_IDP, hook_idp);          plugin_inited = false;     } }  //-------------------------------------------------------------------------- static bool idaapi run(size_t /*arg*/) {     return false; }  //-------------------------------------------------------------------------- const char comment[] = NAME; const char help[] = NAME;  //-------------------------------------------------------------------------- // //      PLUGIN DESCRIPTION BLOCK // //-------------------------------------------------------------------------- plugin_t PLUGIN = {     IDP_INTERFACE_VERSION,     PLUGIN_PROC | PLUGIN_MOD, // plugin flags     init, // initialize      term, // terminate. this pointer may be NULL.      run, // invoke plugin      comment, // long comment about the plugin              // it could appear in the status line              // or as a hint      help, // multiline help about the plugin      NAME, // the preferred short name of the plugin      "" // the preferred hotkey to run the plugin };

Исправляем баги (разбираемся в шаблоне)

Функции print_op() и print_insn() нужны для того, чтобы понять, какие флаги выставлены текущим процессорным модулем для определённых инструкций. Это нужно, если мы хотим найти какие-то флаги для имеющихся опкодов, чтобы потом использовать их при исправлении.

Собственно, телом нашей «исправлялки» является функция hook_idp(). В ней для наших нужд потребуется реализовать три колбэка:

  1. processor_t::ev_ana_insn: нужен, если в процессорном модуле отсутствует реализация некоторых опкодов
  2. processor_t::ev_emu_insn: тут можно создать кросс-рефы на данные/код, на которые ссылаются новые опкоды (либо не ссылаются старые)
  3. processor_t::ev_out_mnem: новые опкоды должны как-то выводиться. Это всё здесь

Функция init_plugin() не даёт загрузиться нашей исправлялке в других процессорных модулях.
Ну и, самое главное — вешаем весь колбэк на события процессорного модуля:

hook_to_notification_point(HT_IDP, hook_idp, NULL);

Трюк с глобальной переменной ana_addr нужен для того, чтобы ana_insn не уходил в рекурсию при попытке получить информацию об инструкции, которую мы не парсить вручную. Да, увы, этот «костыль» тянется очень давно, ещё со старых версий.

Исправление для проблемы №1

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

Исправление проблемы №1

case processor_t::ev_ana_insn: {     insn_t *out = va_arg(va, insn_t*);      if (ana_addr)         break;      ana_addr = 1;      if (ph.ana_insn(out) <= 0)     {         ana_addr = 0;         break;     }      ana_addr = 0;      for (int i = 0; i < UA_MAXOP; ++i)     {         op_t &op = out->ops[i];          switch (op.type)         {         case o_near:         case o_mem:         {             if (out->itype != 0x76 || op.n != 0 ||                 (op.phrase != 0x09 && op.phrase != 0x0A) ||                 (op.addr == 0 || op.addr >= (1 << 23)) ||                 op.specflag1 != 2) // lea table(pc),Ax                 break;              short diff = op.addr - out->ea;             if (diff >= SHRT_MIN && diff <= SHRT_MAX)             {                 out->Op1.type = o_displ;                 out->Op1.offb = 2;                 out->Op1.dtype = dt_dword;                 out->Op1.phrase = 0x5B;                 out->Op1.specflag1 = 0x10;             }         } break;         }     }      return out->size; } break;

Исправление для проблемы №2

Тут всё просто. Просто маскируем адреса на конкретный диапазон: 0xFF0000-0xFFFFFF (для RAM) и 0xC00000-0xC000FF (для видеопамяти VDP). Тут главное фильтровать по типу операнда o_near и o_mem.

Исправление проблемы №2

case processor_t::ev_ana_insn: {     insn_t *out = va_arg(va, insn_t*);      if (ana_addr)         break;      ana_addr = 1;      if (ph.ana_insn(out) <= 0)     {         ana_addr = 0;         break;     }      ana_addr = 0;      for (int i = 0; i < UA_MAXOP; ++i)     {         op_t &op = out->ops[i];          switch (op.type)         {         case o_near:         case o_mem:         {             op.addr &= 0xFFFFFF; // for any mirrors              if ((op.addr & 0xE00000) == 0xE00000) // RAM mirrors                 op.addr |= 0x1F0000;              if ((op.addr >= 0xC00000 && op.addr <= 0xC0001F) ||                 (op.addr >= 0xC00020 && op.addr <= 0xC0003F)) // VDP mirrors                 op.addr &= 0xC000FF;         } break;         }     }      return out->size; } break;

Исправление для проблемы №3

Собственно, чтобы добавить нужный опкод, необходимо:

  1. Определить индексы для новых опкодов. Все новые индексы должны начинаться с CUSTOM_INSN_ITYPE
  2. lineA/lineF опкоды срабатывают, если в коде встречаются байты: 0xA0/0xF0. Значит, читаем один байт
  3. Получить ссылку на вектор-обработчик. В первых 64-х двордах заголовка в моём случае находятся вектора прерываний. На позициях 0x0A и 0x0B находятся обработчики lineA/lineF:
    value = get_dword(0x0A * sizeof(uint32)); // ... value = get_dword(0x0B * sizeof(uint32));
  4. В ev_emu_insn добавляем cref-ы на обработчики и на следующую инструкцию, чтобы code-flow не прерывался:
        insn->add_cref(insn->Op1.addr, 0, fl_CN); // code ref     insn->add_cref(insn->ea + insn->size, insn->Op1.offb, fl_F); // flow ref
  5. В ev_out_mnem выводим наш кастомный опкод:
    const char *mnem = (outbuffer->insn.itype == M68K_linef) ? "line_f" : "line_a"; outbuffer->out_custom_mnem(mnem);

Решение проблемы №3

enum m68k_insn_type_t {     M68K_linea = CUSTOM_INSN_ITYPE,     M68K_linef, }; /* after includes */  case processor_t::ev_ana_insn: {     insn_t *out = va_arg(va, insn_t*);      if (ana_addr)         break;      uint16 itype = 0;     ea_t value = out->ea;     uchar b = get_byte(out->ea);      if (b == 0xA0 || b == 0xF0)     {         switch (b)         {         case 0xA0:             itype = M68K_linea;             value = get_dword(0x0A * sizeof(uint32));             break;         case 0xF0:             itype = M68K_linef;             value = get_dword(0x0B * sizeof(uint32));             break;         }          out->itype = itype;         out->size = 2;          out->Op1.type = o_near;         out->Op1.offb = 1;         out->Op1.dtype = dt_dword;         out->Op1.addr = value;         out->Op1.phrase = 0x0A;         out->Op1.specflag1 = 2;          out->Op2.type = o_imm;         out->Op2.offb = 1;         out->Op2.dtype = dt_byte;         out->Op2.value = get_byte(out->ea + 1);     }      return out->size; } break; case processor_t::ev_emu_insn: {     const insn_t *insn = va_arg(va, const insn_t*);      if (insn->itype == M68K_linea || insn->itype == M68K_linef)     {         insn->add_cref(insn->Op1.addr, 0, fl_CN);         insn->add_cref(insn->ea + insn->size, insn->Op1.offb, fl_F);         return 1;     } } break; case processor_t::ev_out_mnem: {     outctx_t *outbuffer = va_arg(va, outctx_t *);      if (outbuffer->insn.itype != M68K_linea && outbuffer->insn.itype != M68K_linef)         break;      const char *mnem = (outbuffer->insn.itype == M68K_linef) ? "line_f" : "line_a";      outbuffer->out_custom_mnem(mnem);     return 1; } break;

Исправление для проблемы №4

Решается так: находим опкод для инструкции trap, получаем индекс из инструкции индекс, и берём по индексу вектор-обработчик. Получится что-то типа того:

Решение проблемы №4

case processor_t::ev_emu_insn: {     const insn_t *insn = va_arg(va, const insn_t*);      if (insn->itype == 0xB6) // trap #X     {         qstring name;         ea_t trap_addr = get_dword((0x20 + (insn->Op1.value & 0xF)) * sizeof(uint32));         get_func_name(&name, trap_addr);         set_cmt(insn->ea, name.c_str(), false);         insn->add_cref(trap_addr, insn->Op1.offb, fl_CN);         return 1;     } } break;

Исправление для проблемы №5

Тут тоже всё просто: сначала фильтруем по операции movea.w. Затем, если операнд типа word, и ссылается на RAM, делаем ссылку по-крутому, относительно базы 0xFF0000. Выглядеть это будет так:

Исправление проблемы №5

case processor_t::ev_ana_insn: {     insn_t *out = va_arg(va, insn_t*);      if (ana_addr)         break;      ana_addr = 1;      if (ph.ana_insn(out) <= 0)     {         ana_addr = 0;         break;     }      ana_addr = 0;      for (int i = 0; i < UA_MAXOP; ++i)     {         op_t &op = out->ops[i];          switch (op.type)         {         case o_imm:         {             if (out->itype != 0x7F || op.n != 0) // movea                 break;              if (op.value & 0xFF0000 && op.dtype == dt_word) {                 op.value &= 0xFFFF;             }         } break;         }     }      return out->size; } break; case processor_t::ev_emu_insn: {     const insn_t *insn = va_arg(va, const insn_t*);      for (int i = 0; i < UA_MAXOP; ++i)     {         const op_t &op = insn->ops[i];          switch (op.type)         {         case o_imm:         {             if (insn->itype != 0x7F || op.n != 0 || op.dtype != dt_word) // movea                 break;              op_offset(insn->ea, op.n, REF_OFF32, BADADDR, 0xFF0000);         } break;         }     } } break;

Выводы

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

Ссылка на исходник: https://github.com/lab313ru/m68k_fixer


ссылка на оригинал статьи https://habr.com/post/424263/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *