Modernizing IDA Pro: how to make processor module glitches go away




Hi there,

This is my latest article on a topic near and dear to my heart: making IDA Pro more modern and, well, better.

Those familiar with IDA Pro probably know that feeling: there are glitches in the processor modules that you use, you don't have the source code, and they are driving you crazy! Unfortunately, not all of the glitches discussed here qualify as bugs, meaning that the developers are unlikely to ever fix them—unless you fix them yourself.

Localizing the glitches

Note: In this article, I will be looking for bugs and issues in the Motorola M68000 module (which happens to be my favorite, as well as a commonly used one).

Glitch #1: Addressing relative to the PC register. Specifically, the disassembler listing for such instructions is not always correct. Check out this screenshot:


Everything seems fine at first glance. And the glitch does not even interfere with analysis. But the opcode has been disassembled incorrectly. Let's look at this in an online disassembler:


We see that our addressing should be relative to the PC register since the target address of the reference is within the signed short range.

Glitch #2: "Mirrors" for RAM and certain other regions. Since addressing on m68k is 24-bit, all access to high (or low) regions should be re-addressed to the same range as the cross-references.

Glitch #3 (which is more like "missing functionality" than an actual glitch): The so-called lineA (1010) and lineF (1111) emulators. These opcodes did not make it into the main command set, so they have to be handled in a special way by interrupt vectors. The size of the opcodes depends only on the implementation in the handler. I've seen only a two-byte implementation. So we'll be adding this.

Glitch #4: Trap #N instruction" does not give any cref to the trap handlers.

Glitch #5: The movea.w instruction should make a full xref to an address from a word reference, but we get only a word integer.

Fixing the glitches (empty template)


To understand how to fix a particular processor module, you have to know what our abilities are in this regard, and what exactly a "fix" means.

In short: a fix is delivered in the form of a plug-in, which can be written in either Python or C++ (I chose the latter). C++ is less portable but if anyone is willing to take up the task of porting the plug-in to Python, I will be only grateful!

First we create an empty DLL project in Visual Studio: File->New->Project->Windows Desktop Wizard->Dynamic link library (.dll). Select the Empty Project checkbox and clear all the other checkboxes:


We unpack the IDA SDK and indicate it in the Visual Studio macros (here I am using the 2017 version), which makes it easy to refer to in the future. We simultaneously will add a macro for the path to IDA Pro.

Go to View->Other Windows->Property Manager:


Since we are working with SDK version 7.0, compilation will be performed with the x64 compiler. So select Debug | x64->Microsoft.Cpp.x64.user->Properties:


In the User Macros section, click Add Macro. There we will indicate IDA_SDK alongside the path of where we unpacked the SDK:


Now we can do the same with IDA_DIR (the path being for your copy of IDA Pro):


(By default, IDA is installed in %Program Files%, which requires administrator rights.)

Let's also get rid of the Win32 configuration (this article does not cover compilation for x86 systems) and leave just x64.

Create the empty file ida_plugin.cpp, which for the moment contains no code. Now we can select the character set and other settings for C++:




Now add some includes:



And SDK libraries:



Now we drop in the code template:

#include #include #include #include #include #include #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 _DEBUGstatic 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};

Show/Hide

Fixing the bugs (filling out the template)


The print_op() and print_insn() functions are needed so we can see which flags have been set by the current processor module for certain instructions. This is necessary for finding flags for available opcodes so we can use them in our fix.

The body of our "fixer" is the hook_idp() function. For our needs, we will need to implement three callbacks in it:

  1. processor_t::ev_ana_insn: this is needed if the processor module does not implement some opcodes
  2. processor_t::ev_emu_insn: here you can make cross-references to data/code to which new opcodes refer (or old ones do not refer)
  3. processor_t::ev_out_mnem: new opcodes need to get output somehow—so that's what goes on here

The init_plugin() function will stop our fixer from launching on other processor modules.
And most importantly, we hook the whole callback to processor module events:

hook_to_notification_point(HT_IDP, hook_idp, NULL);

The trick with the ana_addr global variable is necessary so that ana_insn does not get stuck in recursion when trying to get information about an instruction that we do not parse manually. This crutch has been around for a long time since early versions, and alas, doesn't seem to be going away anytime soon.

Fix for Glitch #1


Finding a solution required a lot of mucking about with the debugger output that I implemented for the purpose. I knew that in some cases, IDA successfully outputs references relative to PC (for instructions where a jump is made based on an offset table that is near the current instruction plus register index); but proper display of addressing had not been implemented for the lea instruction. Ultimately, I found one such instruction with a jump and figured out which flags are needed to make PC display properly with parentheses:

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;

Fix for Glitch #2


Here things are simple. We simply mask addresses for a specific range: 0xFF0000-0xFFFFFF (for RAM) and 0xC00000–0xC000FF (for VDP video memory). The main thing is to filter by operand type with o_near and 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;

Fix for Glitch #3


To add the opcodes, what we need to do is:

1. Define indexes for the new opcodes. All new indexes should start with: CUSTOM_INSN_ITYPE

enum m68k_insn_type_t{ M68K_linea = CUSTOM_INSN_ITYPE, M68K_linef, };

2. The lineA/lineF opcodes trigger when the following bytes are encountered in the code: 0xA0/0xF0. So we read one byte.

3. Get reference to the handling vector. The interrupt vectors are located in the first 64 dwords of the header, in my case. The lineA/lineF handlers are at positions 0x0A and 0x0B:

value = get_dword(0x0A * sizeof(uint32)); // ...value = get_dword(0x0B * sizeof(uint32));

4. In ev_emu_insn we add cross-references for handlers and the following instruction to avoid interrupting the 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. In ev_out_mnem we output our custom opcode:

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;

Fix for Glitch #4

Find the opcode for the trap instruction, get the index from the instruction, and take the handler vector at that index. The result resembles the following:



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;

Fix for Glitch #5


Things are clear-cut here too: first filter by the movea.w operation. Then, if the operand is of the word type and refers to RAM, we make a snazzy reference relative to the base 0xFF0000. Here's how this looks:



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;

Conclusions


Making fixes to modules can be rather involved, especially when it's something more complicated than simply implementing unknown opcodes.

This can mean hours of debugging the current implementation and trying to figure out how it all works (perhaps with some reversing of the module itself). But the results are well worth it.

Link to code: https://github.com/lab313ru/m68k_fixer

Author: Vladimir Kononovich, Positive Technologies


Article Link: http://blog.ptsecurity.com/2018/10/modernizing-ida-pro-how-to-make.html