Mise à niveau d'IDA Pro. Nous réparons les jambages des modules processeurs


Bonjour à tous


Après un temps assez long depuis la rédaction du premier article, j'ai tout de même décidé, quoique un peu, d'écrire des articles sur le thème de la modification / amélioration d' IDA Pro .


Cet article explique comment fixer correctement les montants dans ces modules de processeur, dont vous n'avez pas la source, mais les montants ne donnent pas en direct. Malheureusement, tous les problèmes répertoriés ci-dessous ne peuvent pas être attribués aux montants, il est donc peu probable que les développeurs les implémentent.


Nous localisons les bugs


Remarque: les erreurs ci-après seront prises en compte dans le module Motorola M68000 (mon préféré et très souvent utilisé).


Donc, le premier cant : l'adressage par rapport au registre PC . L'erreur est que la liste démontée pour de telles instructions n'est pas toujours correcte. Jetez un œil à la capture d'écran:

Il ne semble pas y avoir d'erreur ici. De plus, sa présence n'interfère pas avec l'analyse. Mais, l'opcode n'est pas assemblé correctement. Regardons le dysasme dans un désassembleur en ligne:

Nous voyons que l'adressage doit être relatif au registre PC , car L'adresse de destination du lien se situe dans la plage signed short .


Impossible deux : "miroirs" pour la RAM et certaines autres régions. Parce que Étant donné que l'adressage dans m68k est de 24 bits, tous les appels vers les régions supérieures (ou vice versa, inférieures) doivent être redirigés vers la même plage que les références croisées.


Il y a trois cant (plutôt, même pas un cant, mais le manque de fonctionnalité): les soi-disant émulateurs lineA ( 1010 ) et lineF ( 1111 ). Ce sont de tels opcodes pour lesquels l'ensemble de commandes de base ne suffisait pas, ils doivent donc être traités de manière spéciale par des vecteurs d'interruption. La taille des opcodes dépend uniquement de l'implémentation au niveau du processeur. Je n'ai vu qu'une implémentation sur deux octets. Nous ajouterons.


Jam four : trap #N Les instructions ne donnent pas de crefs aux crochets eux-mêmes.


Jamb cinq : l'instruction movea.w devrait faire une xréf complète à une adresse à partir d'un lien de mot , mais nous n'avons qu'un numéro de mot .


Nous corrigeons des bugs (modèle)


Afin de comprendre comment réparer un module processeur spécifique, vous devez comprendre quelles possibilités nous avons sur ce sujet en principe et ce qui constitue un «correctif».


En fait, le "fixateur" est un plugin ordinaire. Ça, c'est un peu comme ça, peut être écrit en Python , mais j'ai tout fait dans les "plus". Seule la portabilité en souffre, mais si quelqu'un s'engage à réécrire le plugin en Python , je serai très reconnaissant.


Tout d'abord, créez un projet DLL vide dans Visual Studio : Fichier-> Nouveau-> Projet-> Assistant Bureau Windows-> Bibliothèque de liens dynamiques (.dll), également en cochant la case Projet vide et en décochant le reste:


Décompressez le SDK IDA et écrivez-le dans les macros Visual Studio (j'utiliserai 2017 ) afin qu'à l'avenir vous puissiez facilement le référencer. Dans le même temps, nous ajouterons une macro pour le chemin vers IDA Pro .


Allez dans Affichage -> Autres fenêtres -> Gestionnaire de propriétés :


Parce que nous travaillons avec SDK version 7.0 , la compilation se fera par compilateur x64 . Par conséquent, sélectionnez Déboguer | x64 -> Microsoft.Cpp.x64.user -> Propriétés :


Cliquez sur le bouton Ajouter une macro dans la section Macros utilisateur et écrivez-y la macro IDA_SDK avec le chemin où vous avez décompressé le SDK :


Nous faisons de même avec IDA_DIR (le chemin vers votre IDA Pro ):

Je note que l' IDA est défini par défaut sur % Program Files% , ce qui nécessite des droits d'administrateur.


Supprimons également la configuration Win32 (dans cet article, je n'affecterai pas la compilation pour les systèmes x86 ), ne laissant que l'option x64 .


Créez un fichier ida_plugin.cpp vide. Nous n'ajoutons pas encore de code.
Il est maintenant possible de définir l'encodage et d'autres paramètres pour C ++ :




Nous écrivons les inclusions:


Et les bibliothèques du SDK :



Ajoutez maintenant le modèle de code:


Code 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 }; 

Nous corrigeons des bugs (nous comprenons le modèle)


Les fonctions print_op() et print_insn() sont nécessaires pour comprendre quels drapeaux sont définis par le module processeur actuel pour certaines instructions. Ceci est nécessaire si nous voulons trouver des drapeaux pour les opcodes existants, puis les utiliser lors de la correction.


En fait, le corps de notre "correction" est la fonction hook_idp() . Dans ce document, pour nos besoins, nous devons implémenter trois rappels:


  1. processor_t::ev_ana_insn : nécessaire s'il n'y a pas d'implémentation de certains opcodes dans le module processeur
  2. processor_t::ev_emu_insn : ici, vous pouvez créer des références croisées sur les données / code, qui sont référencées par de nouveaux opcodes (ou les anciens ne sont pas référencés)
  3. processor_t::ev_out_mnem : de nouveaux opcodes doivent en quelque sorte être sortis. Tout est là

La fonction init_plugin() notre patch de se charger dans d'autres modules de processeur.
Eh bien, et le plus important - nous raccrochons tout le rappel sur les événements du module processeur:


 hook_to_notification_point(HT_IDP, hook_idp, NULL); 

L'astuce avec la variable globale ana_addr nécessaire pour que ana_insn n'entre pas en récursivité lorsque vous essayez d'obtenir des informations sur une instruction que nous n'analysons pas manuellement. Oui, hélas, cette «béquille» dure très longtemps, même par rapport aux anciennes versions.

Correction du problème numéro 1


Afin de résoudre correctement ce problème, j'ai dû bricoler beaucoup avec la première conclusion, que je viens de mettre en œuvre pour cette tâche. Je savais que dans certains cas, l' IDA affiche avec succès des liens sur le PC (dans les instructions où il y a un saut sur la table de décalage, qui n'est pas loin de l'instruction actuelle, plus un index de cas), mais pour l'instruction lea , le mappage d'adresse n'est pas correctement implémenté. En conséquence, j'ai trouvé une telle instruction de saut et j'ai découvert quels drapeaux doivent être définis pour que le PC avec crochets s'affiche:



Résoudre le problème numéro 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; 

Correction du problème numéro 2


Ici, tout est simple. Masquez simplement les adresses dans une plage spécifique: 0xFF0000-0xFFFFFF (pour la RAM) et 0xC00000-0xC000FF (pour la mémoire vidéo VDP ). L'essentiel ici est de filtrer par type d'opérande o_near et o_mem .


Résoudre le problème numéro 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; 

Correction du problème numéro 3


En fait, pour ajouter l'opcode souhaité, vous devez:


  1. Définissez des indices pour de nouveaux opcodes. Tous les nouveaux index doivent commencer par CUSTOM_INSN_ITYPE
     enum m68k_insn_type_t { M68K_linea = CUSTOM_INSN_ITYPE, M68K_linef, }; 
  2. Les opcodes lineA / lineF sont déclenchés si des octets sont trouvés dans le code: 0xA0 / 0xF0 . Nous lisons donc un octet
  3. Obtenez un lien vers un vecteur de gestionnaire. Dans les 64 premiers mètres de l'en-tête, dans mon cas, il y a des vecteurs d'interruption. Aux positions 0x0A et 0x0B, il y a des gestionnaires lineA / lineF :
     value = get_dword(0x0A * sizeof(uint32)); // ... value = get_dword(0x0B * sizeof(uint32)); 
  4. Dans ev_emu_insn ajoutez des ev_emu_insn aux gestionnaires et à l'instruction suivante afin que le flux de code ne soit pas interrompu:
      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. Dans ev_out_mnem notre opcode personnalisé:
     const char *mnem = (outbuffer->insn.itype == M68K_linef) ? "line_f" : "line_a"; outbuffer->out_custom_mnem(mnem); 


Solution au problème 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; 

Correction du problème 4


Il est résolu de cette façon: nous trouvons l'opcode pour l'instruction trap , nous obtenons l'index de l'instruction, et nous prenons un vecteur gestionnaire de cet index. Vous obtenez quelque chose comme ça:



Solution au problème 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; 

Correction du problème numéro 5


Ici aussi, tout est simple: tout d'abord, filtrer par opération movea.w . Ensuite, si l'opérande est du type mot, et fait référence à la RAM, nous faisons une référence de façon abrupte, par rapport à la base 0xFF0000. Cela ressemblera à ceci:



Résoudre le problème numéro 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; 

Conclusions


En fait, la réparation des modules existants n'est pas une tâche très simple, si elle concerne non seulement l'implémentation d'opcodes inconnus, mais quelque chose de plus compliqué.
Il faut plusieurs heures pour déboguer l'implémentation existante, une compréhension de ce qui s'y passe (parfois même l'inverse du module de pourcentage). Mais le résultat en vaut la peine.


Lien vers la source: https://github.com/lab313ru/m68k_fixer

Source: https://habr.com/ru/post/fr424263/


All Articles