首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第57期->技术专题
期刊号: 类型: 关键词:
IDASDK对指令操作数的识别

作者:san <san _at_ nsfocus.com>
出处:http://www.nsfocus.net
日期:2005-04-04

整理:san

创建:2005.03.28

--[ 1. 相关结构

IDASDK的ua_ana0函数会调用ph.u_ana并且填充一个类型为insn_t的cmd结构。类insn_t在ua.hpp头文件里定义,通过这个类的结构可以轻松得到指定地址的指令类型和操作数等数据。

IDAPro支持的指令类型都在allins.hpp里定义,每一种类型的处理器对应一个枚举。通过比较cmd.itype可以判断指定地址的指令类型是什么。

操作定义在该类里类型为op_t的Operands数组结构,IDASDK允许一条指令最多有6个操作数。类op_t同样在ua.hpp里定义,本文主要要讨论的就是这个结构。类op_t里的type成员非常重要,就在op_t的上面一点定义,下面仔细介绍各种类型:

o_void

说明该操作数不可识别。

o_reg

说明该操作数只是一个寄存器。寄存器号保存在cmd.Operands[i].reg里面,对于x86的处理器来说,寄存器号可以在intel.hpp头文件里RegNo枚举变量中获取。其它类型的处理器,有些参考module目录下的一些头文件。当然,有很多类型的处理器没有资料。

o_mem

说明该操作数是一个引用目标地址的直接内存数据,这个目标虚拟地址保存在cmd.Operands[i].addr里。比如对于指令"mov eax, dword ptr ds:[0x00401000]",第二个操作数的类型是o_mem,用cmd.Operands[1].addr可以取到0x00401000。

o_phrase

说明该操作数是一个引用寄存器内容的内存数据。这种操作数可能包含基址寄存器、索引寄存器和缩放系数。当操作数只有一个寄存器的时候,寄存器号保存在cmd.Operands[i].phrase里。当操作数比较复杂的时候就会用到specflag。比如对于指令"lea eax, [ebx+ecx*4]",第二个操作数的类型是o_phrase。以这个指令为例,这时cmd.Operands[1].specflag1=1,cmd.Operands[1].specflag2=0x8B。specflag2的信息按照下面表格拆分表示:

+-----------------------------------------------+
|  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
+-----------------------------------------------+
|  缩放系数 |    索引寄存器   |    基址寄存器   |
+-----------------------------------------------+

缩放系数的值是这样对应的:

0 eq 0
1 eq 2
2 eq 4
3 eq 8

cmd.Operands[1].specflag2=0x8B拆解为:

+-----------------------------------------------+
|  7  |  6  |  5  |  4  |  3  |  2  |  1  |  0  |
+-----------------------------------------------+
|  1  |  0  |  0  |  0  |  1  |  0  |  1  |  1  |
+-----------------------------------------------+
|   2 -> 4  |     1 -> ecx    |     3 -> ebx    |
+-----------------------------------------------+

用表达式可以这样取:

基址寄存器 - specflag2 & 7
索引寄存器 - (specflag2 >> 3) & 7
缩放系数   - (specflag2 & 0xC0) >> 6

对于x86类型处理器,specflag确实是这样操作,但是对于其它类型的处理器,并不是这样,在小节里会提到。

o_displ

说明操作数是一个引用寄存器内容并且加偏移的内存数据。偏移值保存在cmd.Operands[i].addr里,其它信息同o_phrase。

o_imm

说明操作数是一个立即数。立即数的值保存在cmd.Operands[i].value里。

o_near

说明操作数是一个目标地址,通常用于分支跳转和调用指令的地址。目标虚拟地址保存在cmd.Operands[i].addr里。

o_far

说明操作数是一个跨段地址的引用。

--[ 2. 演示代码

/* oprand.cpp
*
*  Oprand demo plugin for IDAPro
*  Written by san
*
*  Compile with:
*  cl oprand.cpp /I "..\IDAPro\idasdk47\include" /D __NT__ /D __IDP__ /GX /link /dll "..\IDAPro\idasdk47\libvc.w32\ida.lib" /OUT:oprand.plw /export:PLUGIN
*/

#include <ida.hpp>
#include <idp.hpp>
#include <loader.hpp>
#include <ua.hpp>
#include <allins.hpp>
#include <lines.hpp>

void test()
{
    int i;
    char ins[256];

    ua_ana0(get_screen_ea());
    generate_disasm_line(cmd.ea, ins, sizeof(ins));
    tag_remove(ins, ins, sizeof(ins));
    
    msg("%08X \"%s\" <==\n  itype: %d\n  size : %d\n  pref : 0x%x\n",
        cmd.ea, ins, cmd.itype, cmd.size, cmd.auxpref);

    for (i=0; i<UA_MAXOP; i++) {
        if (!cmd.Operands[i].type) break;
        msg("  Op%d  : %d\n", i+1, cmd.Operands[i].type);

        switch (cmd.Operands[i].type)
        {
            case o_reg:
                msg("    reg:%d dtyp:%d\n", cmd.Operands[i].reg, cmd.Operands[i].dtyp);
                break;
            case o_mem:
                msg("    mem:0x%x dtyp:%d\n", cmd.Operands[i].addr, cmd.Operands[i].dtyp);
                break;
            case o_phrase:
                msg("    phrase:0x%x dtyp:%d specflag1:0x%x specflag2:0x%x",
                    cmd.Operands[i].phrase, cmd.Operands[i].dtyp, cmd.Operands[i].specflag1, cmd.Operands[i].specflag2);
                if (cmd.Operands[i].specflag1) {
                    msg(" base_reg:%d index_reg:%d scale:%x",
                        (cmd.Operands[i].specflag2 & 7),
                        ((cmd.Operands[i].specflag2 >> 3) & 7),
                        ((cmd.Operands[i].specflag2 & 0xC0) >> 6)
                        );
                }
                msg("\n");
                break;
            case o_displ:
                msg("    displ:0x%x dtyp:%d specflag1:0x%x specflag2:0x%x",
                    cmd.Operands[i].addr, cmd.Operands[i].dtyp, cmd.Operands[i].specflag1, cmd.Operands[i].specflag2);
                if (cmd.Operands[i].specflag1) {
                    msg(" base_reg:%d index_reg:%d scale:%x",
                        (cmd.Operands[i].specflag2 & 7),
                        ((cmd.Operands[i].specflag2 >> 3) & 7),
                        ((cmd.Operands[i].specflag2 & 0xC0) >> 6)
                        );
                }
                else {
                    msg(" reg:%d", cmd.Operands[i].reg);
                }
                msg("\n");
                break;
            case o_imm:
                msg("    imm:0x%x\n", cmd.Operands[i].value);
                break;
            case o_far:
                msg("    far:0x%x\n", cmd.Operands[i].addr);
                break;
            case o_near:
                msg("    near:0x%x\n", cmd.Operands[i].addr);
                break;
            default:
                msg("    other\n");
                break;
        }
    }
}

/////////////////////////////////////////////////
// Internel Functions
/////////////////////////////////////////////////

int idaapi init(void)
{
    return PLUGIN_OK;
}

void idaapi term(void)
{
}

void idaapi run(int arg)
{
    test();
}

char comment[] = "";

char help[] = "";

char wanted_name[] = "Get Oprand";

char wanted_hotkey[] = "Alt-7";

plugin_t PLUGIN =
{
  IDP_INTERFACE_VERSION,
  0,
  init,
  term,
  run,
  comment,
  help,
  wanted_name,
  wanted_hotkey
};

--[ 3. 小结

对于"rep movsd"指令的输出结果是这样的:

00401033 "rep movsd" <==
  itype: 124
  size : 2
  pref : 0x180a
  Op1  : 3
    phrase:0x7 dtyp:2 specflag1:0x0 specflag2:0x0
  Op2  : 3
    phrase:0x6 dtyp:2 specflag1:0x0 specflag2:0x0

它的指令类型是NN_movs,通过比较"cmd.auxpref && aux_rep"就能得出是否有rep前缀。比较cmd.Operands[i].dtyp值,得出操作数都是32位的。两个操作数都是o_phrase类型,cmd.Operands[i].specflag1的值为0,所以cmd.Operands[i].phrase保存了操作数寄存器号。

对于其它处理器,specflag的操作却和x86是不同的。比如一条ARM指令的输出结果:

00011194 "LDRNE   R6, [R5,R6,LSL#2]" <==
  itype: 30
  size : 4
  pref : 0x0
  Op1  : 1
    reg:6 dtyp:2
  Op2  : 3
    phrase:0x5 dtyp:2 specflag1:0x6 specflag2:0x0 base_reg:0 index_reg:0 scale:0

对于ARM处理器,IDAPro不再是用specflag2的值分解,而是用specflag1。不知道第二个操作数的"LSL#2"如何表现出来,specflag3和specflag4的值也都是0。

对于操作数类型是o_idpspec的资料更加少,比如下面的这个ARM指令:

000111A4 "LDMFD   SP!, {R4-R6,PC}" <==
  itype: 33
  size : 4
  pref : 0xc0
  Op1  : 1
    reg:13 dtyp:2
  Op2  : 10
    other

第二个操作数的type是o_idpspec2,这时它的所有specflag值也都是0。

--[ 4. 参考资料

idasdk47
http://www.cracklab.ru/art/cdee.php
http://reng.ru/tools/005/art.html

版权所有,未经许可,不得转载