Привет всем,
Спустя довольно-таки продолжительное время с момента написания первой статьи я всё-таки решил, пусть и по чуть-чуть, но писать статьи на тему модификации/улучшения 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:
Теперь добавим шаблон кода:
#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()
. В ней для наших нужд потребуется реализовать три колбэка:
processor_t::ev_ana_insn
: нужен, если в процессорном модуле отсутствует реализация некоторых опкодовprocessor_t::ev_emu_insn
: тут можно создать кросс-рефы на данные/код, на которые ссылаются новые опкоды (либо не ссылаются старые)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 со скобками отображался:
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
.
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
Собственно, чтобы добавить нужный опкод, необходимо:
- Определить индексы для новых опкодов. Все новые индексы должны начинаться с
CUSTOM_INSN_ITYPE
enum m68k_insn_type_t { M68K_linea = CUSTOM_INSN_ITYPE, M68K_linef, };
- lineA/lineF опкоды срабатывают, если в коде встречаются байты: 0xA0/0xF0. Значит, читаем один байт
- Получить ссылку на вектор-обработчик. В первых 64-х двордах заголовка в моём случае находятся вектора прерываний. На позициях 0x0A и 0x0B находятся обработчики lineA/lineF:
value = get_dword(0x0A * sizeof(uint32)); // ... value = get_dword(0x0B * sizeof(uint32));
- В
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
- В
ev_out_mnem
выводим наш кастомный опкод:
const char *mnem = (outbuffer->insn.itype == M68K_linef) ? "line_f" : "line_a"; outbuffer->out_custom_mnem(mnem);
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
, получаем индекс из инструкции, и берём по этому индексу вектор-обработчик. Получится что-то типа того:
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. Выглядеть это будет так:
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