标题 简介 类型 公开时间
关联规则 关联知识 关联工具 关联文档 关联抓包
参考1(官网)
参考2
参考3
详情
[SAFE-ID: JIWO-2026-3275]   作者: 小螺号 发表于: [2023-02-23]

本文共 [395] 位读者顶过

在逆向工程期间,你可能会遇到可用工具尚不支持你正在使用的架构的情况。 [出自:jiwo.org]

在本文中,我们探讨了这种情况并展示了扩展 IDA 功能的示例。我们探索如何实现用于反汇编新 Xtensa 指令的 IDA 插件。本指南对于从事涉及逆向工程的项目并希望了解如何充分利用逆向工具的开发人员很有帮助。

为什么要创建自定义 IDA 插件?

在我们之前的一篇文章中,我们讨论了研究固件架构的重要性。在那种情况下,我们设法找到了支持我们需要分析的设备架构的反汇编工具。现在,我们要解决逆向工程工具不支持你在项目中使用的架构的问题。让我们以 IDA 为例来探讨本文。

Interactive Disassembler (IDA)是一种软件反汇编程序,可从机器可执行代码生成汇编语言源代码并执行自动代码分析。这个逆向工程工具通过众多插件提供了广泛的反汇编和调试功能。

IDA 还使用一组称为处理器模块的插件将原始字节代码转换为反汇编文本。每个插件都是为特定的硬件架构设计的。

由于反汇编插件的可用性在很大程度上取决于架构的流行程度,因此某些处理器模块的更新频率高于其他处理器模块。而对新指令的支持可能需要 IDA 开发人员实施。

那么,如果你当前使用的体系结构没有插件,你该怎么办?

好消息是不必等待 IDA 开发人员实现对特定指令集的支持。你可以自己创建自定义 IDA 插件或使用 Python 通过 IDA SDK 实现相关插件。 让我们探索一个为逆向工程目的实现 IDA 插件的示例,并使用新的 Xtensa 架构指令,在撰写本文时 IDA 7.7 不支持这些指令。由于这些指令未在 IDA 中反汇编,因此你将它们视为原始字节:

屏幕截图 1. Xtensa 指令在 IDA 中显示为原始字节

但是如果你使用其他支持反汇编新 Xtensa 指令的软件,比如Lauterbach Trace32 模拟器,你可以看到这些字节是无符号商 (QUOU) 指令:

屏幕截图 2. Xtensa 指令看起来像 Lauterbach Trace32 模拟器中的 QUOU 指令

一旦你知道这些字节是什么,你就可以找到 QUOU 指令的描述,并为 IDA 实现一个插件来扩展现有处理器模块的功能。让我们探讨如何做到这一点。

使用新插件向 IDA 处理器模块添加指令

让我们使用NECromancer插件,它为 NEC V850 CPU 扩展了 IDA 的处理器模块。

使用此插件的目的是挂钩处理器模块的事件处理程序并执行你自己的处理例程而不是现有的处理例程。该插件将允许处理器模块处理默认情况下无法处理的未知指令。

让我们来看看一个空的插件。下面是在 IDA 引擎中注册插件并挂钩处理器模块所需的最少代码:

class XtensaESP(plugin_t):
    flags = PLUGIN_PROC | PLUGIN_HIDE
    comment = ""
    wanted_hotkey = ""
    help = "Adds support for additional Xtensa instructions"
    wanted_name = "XtensaESP"
 
    def __init__(self):
        self.prochook = None
 
    def init(self):
        if ph_get_id() != PLFM_XTENSA:
            return PLUGIN_SKIP
 
        self.prochook = xtensa_idp_hook_t()
        self.prochook.hook()
        print ("%s initialized." % XtensaESP.wanted_name)
        return PLUGIN_KEEP
 
    def run(self, arg):
        pass
 
    def term(self):
        if self.prochook:
            self.prochook.unhook()
 
#--------------------------------------------------------------------------
def PLUGIN_ENTRY():
    return XtensaESP()

为确保 IDA 仅在加载 Xtensa CPU 处理器模块时运行插件,插件执行以下检查:

if ph_get_id() != PLFM_XTENSA

NECromancer 插件还需要 xtensa_idp_hook_t 挂钩类来安装处理器模块事件的处理程序。下面是钩子类主体的样子:

class xtensa_idp_hook_t(IDP_Hooks):
    def __init__(self):
        IDP_Hooks.__init__(self)
 
    def ev_ana_insn(self, insn):
        pass
 
    def ev_out_mnem(self, outctx):
        pass
 
    def ev_out_operand(self, outctx, op):
        pass

此代码段的关键元素是:

  • ev_ana_insn方法,可帮助你分析字节码并创建指令 类
  • ev_out_mnem方法,它允许你创建指令的可视化表示,即生成反汇编程序 文本
  • ev_out_operand方法,实现将指令操作数生成为文本,用于反 汇编

让我们一一实现这三种方法。

1.实现ev_ana_insn方法

使用 NECromancer 插件的目的是添加对 QUOU(无符号商)指令的支持。这意味着你需要知道 CPU 实际上是如何解析代表 QUOU 指令的字节的。

[你可以在Xtensa 指令集架构 (ISA) 参考手册 PDF]中找到此信息:

  • 指令词:

  • 所需的配置选项:32 位整数除法选项

  • 汇编语法:QUOU ar, as, at

  • 说明:将地址寄存器的内容除以地址寄存器的内容QUOU进行 32 位无符号除法,并将商写入地址寄存器。如果地址寄存器的内容是引发整数除以零异常而不是写入结果。as``at``ar``at``0, QUOU

在这种特殊情况下,你不需要详细了解指令的作用。目标是了解 CPU 如何知道一组字节实际上是 QUOU 指令。

IDA 将此 QUOU 指令显示为字节序列:0xC0, 0x22, 0xC2。指令的第一个字节------------0xC0在文档中表示如下:

让我们解释一下这意味着什么:

  1. 标记为 的四个最高字节t的值为0xC。
  2. 较低的四个字节始终等于0。
  3. 的值t是用作指令的第三个参数的寄存器的索引。
  4. 0xC = 12, 这意味着第三个参数是a12.

指令的第二个字节指定了另外两个标记为r和 的参数s:

在我们的例子中,第二个字节是0x22,这意味着r = 0x2和s = 0x2。因此,第一个和第二个操作数都是a2。

最后,第三个字节是0xC2。根据文档,它将始终是一个常量:

由于1100 0010 = 0xC2,你可以使用此字节进行 QUOU 指令标识。

现在,一切准备就绪,可以开始实施 ev_ana_insn 方法了。

首先,创建新的指令 ID,这将允许 IDA 引擎区分新指令和现有指令:

class NewInstructions:
    (NN_quou,
    NN_last) = range(CUSTOM_INSN_ITYPE, CUSTOM_INSN_ITYPE+2)

其次,让我们使用从值开始的 IDCUSTOM_INSN_ITYPE。

最后,运行指令分析方法,如下所示:

def ev_ana_insn(self, insn):
    buf = get_bytes(insn.ea, 3)
    if buf[2] == 0xC2 and (buf[0] & 0xF) == 0:
       insn.itype = NewInstructions.NN_quou
       insn.size = 3
       return True
  
    return False

让我们解释一下这段代码的作用:

  • get_bytes () 函数从 IDA 期望的下一条指令地址处的二进制文件中读取原始字节。
  • 然后,它检查指令字节是否真的看起来像 QUOU 指令。

在这种情况下,我们检查第三个字节是否在第一个字节中,0xC2低 4 个字节是否在文档中定义。0最后,你需要在 ev_ana_insn方法的 insn 参数中填写有关 QUOU 指令的信息:至少指定指令 ID 和以字节为单位的指令大小。

然后,如果 ev_ana_insn 方法能够在建议的地址找到指令,则它必须返回 True ; 否则,它必须返回 False 。

即使你将努力保持在绝对最低限度(如上所示),IDA 也已经能够识别新指令。但我们也想向你展示如何让 IDA 也知道指令参数,否则指令将显示为没有参数。为此,你需要对 ev_ana_insn() 方法进行改进:

def ev_ana_insn(self, insn):
    buf = get_bytes(insn.ea, 3)
    if buf[2] == 0xC2 and (buf[0] & 0xF) == 0:
       insn.itype = NewInstructions.NN_quou
       insn.size = 3
  
       insn.Op1.type = o_reg
       insn.Op1.reg = buf[1] >> 4
       insn.Op2.type = o_reg
       insn.Op2.reg = buf[1] & 0xF
       insn.Op3.type = o_reg
       insn.Op3.reg = buf[0] >> 4
 
       return True
  
    return False

这段新代码实现了指令参数的定义。这些是代码从指令字节中提取的r、s和值。t

解析完成后,就可以设置反汇编文本的输出了。

2.实现ev_out_mnem方法

要生成反汇编文本,你可以完全重用ev_out_mnem 方法的 NECromancer 插件的现有代码:

DEBUG_PLUGIN = True
 
NEWINSN_COLOR = COLOR_MACRO if DEBUG_PLUGIN else COLOR_INSN
 
class NewInstructions:
   (NN_quou,
   NN_last) = range(CUSTOM_INSN_ITYPE, CUSTOM_INSN_ITYPE+2)
    
   lst = {NN_quou:"quou"}
     
def ev_out_mnem(self, outctx):
        insntype = outctx.insn.itype
        global NEWINSN_COLOR
 
        if (insntype >= CUSTOM_INSN_ITYPE) and (insntype in NewInstructions.lst):
            mnem = NewInstructions.lst[insntype]
            outctx.out_tagon(NEWINSN_COLOR)
            outctx.out_line(mnem)
            outctx.out_tagoff(NEWINSN_COLOR)
 
            # TODO: how can MNEM_width be determined programmatically?
            MNEM_WIDTH = 8
            width = max(1, MNEM_WIDTH - len(mnem))
            outctx.out_line(' ' * width)
 
            return True
        return False

我们来解释一下这个例子的要点:

  • ev_out_mnem从 NewInstructions 类中获取指令名,并andoutctx.out_line在 IDA 反汇编窗口中显示文本。
  • 该DEBUG_PLUGIN标志更改文本的颜色。可以设置为默认颜色,也可以设置为Macros的颜色,使新指令突出显示,在调试插件时非常方便。
  • ev_out_mnem 输出八个空格来准备引擎输出指令参数。因此,一切------代码、空格、代码注释------都将对齐。

3.实现ev_out_operand方法

要实现指令操作数的生成,你还可以重用ev_out_operand 方法的现成代码:

def ev_out_operand(self, outctx, op):
        insn = outctx.insn
        if insn.itype in [NewInstructions.NN_ld_hu, NewInstructions.NN_st_h]:
            if op.type == o_displ:
                outctx.out_value(op, OOF_ADDR)
                outctx.out_register(ph_get_regnames()[op.reg])
                return True
        return False

此代码检查指令是否是你之前添加的指令。如果指令包含参数,则需要打印操作数。然后,你将获得寄存器的名称,插件将打印它。

现在,所有准备工作都已完成,你可以开始测试你创建的插件了。

测试插件

将插件放在\IDA\plugins目录中,以便 IDA 可以运行它。然后,加载包含未定义字节的 IDA 数据库,将光标置于其上,然后按 C 键创建指令:

屏幕截图 3. 在 IDA 中创建新的 QUOU 指令

添加对 QUOU 指令的支持后,IDA 会在遇到该指令时自动识别它。此处指令的颜色与其余指令不同,因为我们DEBUG_PLUGIN为此示例启用了标志。如果你决定禁用该标志,指令的颜色将与代码的其余部分相同。

我们示例的完整 NECromancer 插件源代码如下:

from ida_lines import COLOR_INSN, COLOR_MACRO 
from ida_idp import CUSTOM_INSN_ITYPE, IDP_Hooks, ph_get_regnames, ph_get_id, PLFM_XTENSA
from ida_bytes import get_bytes
from ida_idaapi import plugin_t, PLUGIN_PROC, PLUGIN_HIDE, PLUGIN_SKIP, PLUGIN_KEEP
from ida_ua import o_displ, o_reg, o_imm, dt_dword, OOF_ADDR
from struct import unpack

DEBUG_PLUGIN = True

NEWINSN_COLOR = COLOR_MACRO if DEBUG_PLUGIN else COLOR_INSN

class NewInstructions:
    (NN_quou,
    NN_muluh) = range(CUSTOM_INSN_ITYPE, CUSTOM_INSN_ITYPE+2)
    
    lst = {NN_quou:"quou",
           NN_muluh:"muluh"}
#--------------------------------------------------------------------------
class xtensa_idp_hook_t(IDP_Hooks):
    def __init__(self):
        IDP_Hooks.__init__(self)

    def decode_instruction(self, insn):
        buf = get_bytes(insn.ea, 3)
        #print("%08X bytes %X %X %X" % (insn.ea , buf[2] , buf[1] , buf[0]))
        if buf[2] == 0xC2 and (buf[0] & 0xF) == 0:
            insn.itype = NewInstructions.NN_quou
            insn.size = 3
            insn.Op1.type = o_reg
            insn.Op1.reg = buf[1] >> 4
            insn.Op2.type = o_reg
            insn.Op2.reg = buf[1] & 0xF 
            insn.Op3.type = o_reg
            insn.Op3.reg = buf[0] >> 4
            return True
            
        if buf[2] == 0xA2 and (buf[0] & 0xF) == 0:
            insn.itype = NewInstructions.NN_muluh
            insn.size = 3
            insn.Op1.type = o_reg
            insn.Op1.reg = buf[1] >> 4
            insn.Op2.type = o_reg
            insn.Op2.reg = buf[1] & 0xF 
            insn.Op3.type = o_reg
            insn.Op3.reg = buf[0] >> 4
            return True
             
        return False

    def ev_ana_insn(self, insn):
        return self.decode_instruction(insn)

    def ev_out_mnem(self, outctx):
        insntype = outctx.insn.itype
        global NEWINSN_COLOR

        if (insntype >= CUSTOM_INSN_ITYPE) and (insntype in NewInstructions.lst):
            mnem = NewInstructions.lst[insntype]
            outctx.out_tagon(NEWINSN_COLOR)
            outctx.out_line(mnem)
            outctx.out_tagoff(NEWINSN_COLOR)

            # TODO: how can MNEM_width be determined programmatically?
            MNEM_WIDTH = 8
            width = max(1, MNEM_WIDTH - len(mnem))
            outctx.out_line(' ' * width)

            return True
        return False


    def ev_out_operand(self, outctx, op):
        insn = outctx.insn
        if insn.itype in [NewInstructions.NN_ld_hu, NewInstructions.NN_st_h]:
            if op.type == o_displ:
                outctx.out_value(op, OOF_ADDR)
                outctx.out_register(ph_get_regnames()[op.reg])
                return True
        return False
#--------------------------------------------------------------------------
class XtensaESP(plugin_t):
    flags = PLUGIN_PROC | PLUGIN_HIDE
    comment = ""
    wanted_hotkey = ""
    help = "Adds support for additional Xtensa instructions"
    wanted_name = "XtensaESP"

    def __init__(self):
        self.prochook = None

    def init(self):
        if ph_get_id() != PLFM_XTENSA:
            return PLUGIN_SKIP

        self.prochook = xtensa_idp_hook_t()
        self.prochook.hook()
        print ("%s initialized." % XtensaESP.wanted_name)
        return PLUGIN_KEEP

    def run(self, arg):
        pass

    def term(self):
        if self.prochook:
            self.prochook.unhook()
#--------------------------------------------------------------------------
def PLUGIN_ENTRY():
    return XtensaESP()

这就是你如何使用 Python 为 IDA 实现插件。现在你知道了:IDA 能够反汇编新的 Xtensa 指令。

结论

尽管像 IDA 这样的工具支持大多数流行的架构,但了解如何扩展逆向工程工具的功能将增加你的机会。这些知识还将在你使用新架构和解决重要任务时提供帮助。

评论

暂无
发表评论
 返回顶部 
热度(395)
顶 踩
 关注微信