/* Plugin that allows the user to specify the exact address and shape of a jump table (switch idiom). It displays a dialog box with the most important attributes of the switch idiom. If the idiom is complex and has more attributes, then more dialog boxes are displayed. All collected information is validated and then stored in the database in the switch_info_t structure. The last step is to reanalyze the switch idiom. Please note that this plugin supports the most common switch idiom but some idiom types are not handled, for example, custom switches are not. */ #include #include #include #include //------------------------------------------------------------------------- struct plugin_ctx_t; #define ACTION_NAME "uiswitch:SpecSwitchIdiom" struct uiswitch_ah_t : public action_handler_t { plugin_ctx_t &plg; uiswitch_ah_t(plugin_ctx_t &_plg) : plg(_plg) {} virtual int idaapi activate(action_activation_ctx_t *) override; virtual action_state_t idaapi update(action_update_ctx_t *ctx) override { return ctx->widget_type == BWN_DISASM ? AST_ENABLE_FOR_WIDGET : AST_DISABLE_FOR_WIDGET; } }; //-------------------------------------------------------------------------- struct plugin_ctx_t : public plugmod_t, public ignore_micro_t { uiswitch_ah_t uiswitch_ah = uiswitch_ah_t(*this); plugin_ctx_t(); virtual bool idaapi run(size_t) override; bool callback(); }; //------------------------------------------------------------------------- int idaapi uiswitch_ah_t::activate(action_activation_ctx_t *) { return plg.callback(); } //--------------------------------------------------------------------------- // The main form // hotkeys: abdefginpstu static const char main_form[] = "HELP\n" "Please specify the jump table address, the number of its\n" "elements and their widths(1,2,4,8). The element shift amount and base value\n" "should be specified only if the table elements are not\n" "plain target addresses but must be converted using the following\n" "formula:\n" "\n" " target = base +/- (table_element << shift)\n" "\n" "(only this formula is supported by the kernel; other cases must be\n" "handled by plugins and 'custom' switch idioms).\n" "\n" "If you specify BADADDR as the element base then the base of the\n" "switch segment will be used\n" "\n" "The start of the switch idiom is the address of the first instruction\n" "in the switch idiom.\n" "\n" "Subtraction is used instead of addition if \"Subtract table elements\"\n" "is selected.\n" "\n" "When table element is an instruction then you should select\n" "\"Table element is insn\".\n" "\n" "If you specify that a separate value table is present, an additional\n" "dialog box with its attributes will be displayed.\n" "ENDHELP\n" // ansebtifdpgul "Manual switch declaration - Main features\n" "\n" "<~A~ddress of jump table :N::18::>\n" "<~N~umber of elements :D::18::>\n" "<~S~ize of table element :D::18::>\n" "<~E~lement shift amount :D::18::>\n" "\n" "\n" "\n" "<~I~nput register of switch :q:511:18::>\n" "<~F~irst(lowest) input value:D::18::>(if value table is absent)\n" "<~D~efault jump address :N::18::>\n" "\n" "\n" "\n" "\n" ">\n" "\n" "\n"; // this form displayed if the value table is present // shortcuts: afinus static const char value_form[] = "HELP\n" "Direct value table holds values of the switch 'case's.\n" "Each value maps to the corresponding target of the jump table\n" "Indirect value table holds indexes into jump table.\n" "\n" "Inversed value table maps the first element of the value table\n" "to the last element of the jump table.\n" "\n" "For direct table the size of the value table is equal\n" "to the size of the jump table.\n" "\n" "Example of switch idiom with indirect value table:\n" "\n" " cmp ecx, 0Fh\n" " ja short defjump\n" " movzx ecx, ds:indirect_value_table[ecx]\n" " jmp ds:jump_table[ecx*4]\n" "\n" " jump_table dd offset target_1\n" " dd offset target_2\n" " indirect_value_table db 0, 0, 1, 0\n" " db 1, 1, 1, 0\n" " db 1, 1, 1, 1\n" " db 1, 1, 1, 0\n" "\n" "ENDHELP\n" "Manual switch declaration - Value table\n" "\n" "<~I~ndirect value table:C>\n" ">\n" "<~A~ddress of value table:N::18::>\n" " (only for indirect table)\n" "<~S~ize of table element :D::18::>\n" "<~F~irst(lowest) input value:D::18::> (only for indirect table)\n" "\n" "\n"; //--------------------------------------------------------------------------- // Validate table attributes static bool check_table(ea_t table, uval_t elsize, uval_t tsize) { flags_t F; if ( getseg(table) == NULL || is_code((F=get_flags(table))) || is_tail(F) ) { warning("AUTOHIDE NONE\nIncorrect table address %a", table); return false; } if ( elsize != 1 && elsize != 2 && elsize != 4 && elsize != 8 ) { warning("AUTOHIDE NONE\nIncorrect table element size %" FMT_EA "u", elsize); return false; } flags_t DF = get_flags_by_size((size_t)elsize); if ( !can_define_item(table, elsize*tsize, DF) ) { warning("AUTOHIDE NONE\nCannot create table at %a size %" FMT_EA "u", table, tsize); return false; } return true; } //--------------------------------------------------------------------------- // The main function - called when the user selects the menu item bool plugin_ctx_t::callback() { // Calculate the default values to display in the form ea_t screen_ea = get_screen_ea(); segment_t *s = getseg(screen_ea); if ( s == NULL || !is_code(get_flags(screen_ea)) ) { warning("AUTOHIDE NONE\nThe cursor must be on the table jump instruction"); return false; } // If switch information is present in the database, use it for defaults switch_info_t si; if ( get_switch_info(&si, screen_ea) <= 0 ) { si.jumps = get_first_dref_from(screen_ea); unsigned int jsize = (int)s->abytes(); si.set_jtable_element_size(jsize); // calculate NCASES if ( si.jumps != BADADDR ) { const segment_t *jtable_seg = getseg(si.jumps); ea_t jtable_end = jtable_seg != nullptr ? jtable_seg->end_ea : BADADDR; int size = int((jtable_end - si.jumps) / jsize); si.ncases = size > USHRT_MAX ? USHRT_MAX : size; trim_jtable(&si, screen_ea, false); } // calculate STARTEA si.startea = screen_ea; while ( true ) { ea_t prev = prev_not_tail(si.startea); if ( !is_switch_insn(prev) ) break; si.startea = prev; } } ea_t jumps = si.jumps; uval_t jtsize = si.ncases; ea_t startea = si.startea; uval_t elbase = si.elbase; uval_t jelsize = si.get_jtable_element_size(); uval_t shift = si.get_shift(); ea_t defea = si.defjump; qstring input; if ( si.regnum != -1 ) get_reg_name(&input, si.regnum, get_dtype_size(si.regdtype)); ushort jflags = 0; if ( si.flags & SWI_SIGNED ) jflags |= 2; if ( si.flags & SWI_SUBTRACT ) jflags |= 4; if ( si.flags & SWI_JMPINSN ) jflags |= 8; uval_t lowcase = 0; ushort vflags = 0; ea_t vtable = BADADDR; ea_t vtsize = 0; ea_t velsize = 0; ea_t vlowcase = 0; if ( si.flags & SWI_SPARSE ) { jflags |= 1; vtable = si.values; vtsize = jtsize; velsize = si.get_vtable_element_size(); if ( si.flags & SWI_INDIRECT ) { vlowcase = si.get_lowcase(); vflags |= 1; jtsize = si.jcases; } if ( si.flags & SWI_JMP_INV ) vflags |= 2; } else { lowcase = si.lowcase; } // TODO allow to change these fields ea_t expr_ea = si.expr_ea; eavec_t marks = si.marks; // Now display the form and let the user edit the attributes while ( ask_form(main_form, &jumps, &jtsize, &jelsize, &shift, &elbase, &startea, &input, &lowcase, &defea, &jflags) ) { if ( !check_table(jumps, jelsize, jtsize) ) continue; if ( shift > 3 ) { warning("AUTOHIDE NONE\nInvalid shift value (allowed values are 0..3)"); continue; } if ( !is_code(get_flags(startea)) ) { warning("AUTOHIDE NONE\nInvalid switch idiom start %a (must be an instruction", startea); continue; } reg_info_t ri; ri.reg = -1; ri.size = 0; if ( !input.empty() && !parse_reg_name(&ri, input.c_str()) ) { warning("AUTOHIDE NONE\nUnknown input register: %s", input.c_str()); continue; } if ( defea != BADADDR && !is_code(get_flags(defea)) ) { warning("AUTOHIDE NONE\nInvalid default jump %a (must be an instruction", defea); continue; } if ( jflags & 1 ) // value table is present { bool vok = false; while ( ask_form(value_form, &vflags, &vtable, &vtsize, &velsize, &vlowcase) ) { if ( (vflags & 1) == 0 ) vtsize = jtsize; if ( check_table(vtable, velsize, vtsize) ) { vok = true; break; } } if ( !vok ) break; } // ok, got and validated all params -- fill the structure si.clear(); if ( jflags & 2 ) si.flags |= SWI_SIGNED; if ( jflags & 4 ) si.flags |= SWI_SUBTRACT; if ( jflags & 8 ) si.flags |= SWI_JMPINSN; si.jumps = jumps; si.ncases = ushort(jtsize); si.startea = startea; if ( elbase != BADADDR ) si.set_elbase(elbase); si.set_jtable_element_size((int)jelsize); si.set_shift((int)shift); si.defjump = defea; if ( ri.reg != -1 ) si.set_expr(ri.reg, get_dtype_by_size(ri.size)); if ( jflags & 1 ) // value table is present { si.flags |= SWI_SPARSE; si.values = vtable; si.set_vtable_element_size((int)velsize); if ( (vflags & 1) != 0 ) { si.flags |= SWI_INDIRECT; si.jcases = (int)jtsize; si.ncases = (ushort)vtsize; si.ind_lowcase = vlowcase; } if ( (vflags & 2) != 0 ) si.flags |= SWI_JMP_INV; } else { si.lowcase = lowcase; } si.expr_ea = expr_ea; si.marks = marks; si.flags |= SWI_USER; // ready, store it set_switch_info(screen_ea, si); create_switch_table(screen_ea, si); create_insn(screen_ea); info("AUTOHIDE REGISTRY\nSwitch information has been stored"); break; } return true; } //-------------------------------------------------------------------------- static plugmod_t *idaapi init() { return new plugin_ctx_t; } //-------------------------------------------------------------------------- plugin_ctx_t::plugin_ctx_t() { register_and_attach_to_menu( "Edit/Other/Create", ACTION_NAME, "Specify switch idiom...", NULL, SETMENU_INS, &uiswitch_ah, this, ADF_OT_PLUGMOD); init_ignore_micro(); } //-------------------------------------------------------------------------- bool idaapi plugin_ctx_t::run(size_t) { callback(); return true; } //-------------------------------------------------------------------------- static const char help[] = ""; static const char comment[] = ""; static const char wanted_name[] = "Specify switch idiom"; static const char wanted_hotkey[] = ""; plugin_t PLUGIN = { IDP_INTERFACE_VERSION, PLUGIN_MULTI // The plugin can work with multiple idbs in parallel | PLUGIN_HIDE, // Plugin should not appear in the Edit, Plugins menu init, // initialize nullptr, nullptr, comment, // long comment about the plugin help, // multiline help about the plugin wanted_name, // the preferred short name of the plugin wanted_hotkey // the preferred hotkey to run the plugin };