在 IDA 中,有没有办法将动态导入函数的引用添加到“导入”选项卡中?

逆向工程 艾达 蟒蛇 进口改造 ida插件
2021-06-29 02:20:02

标题说了大部分。假设我有一个 Windows PE(x86,32 位)二进制文件(只是为了让我们讨论案例),导入列表通常只会显示在导入目录中找到的导入。它显示的属性是从中导入它的函数、名称和库的地址,如以下屏幕截图片段所示:

IDA Pro 中导入选项卡的屏幕截图

有没有办法通过脚本编写(IDC 或 Python,我不太在意),将我自己的导入添加到列表中,例如,让它们指向(地址属性)这样的代码(突出显示的行)?:

IDA Pro 中的动态导入函数

即在这种情况下,该行将如下所示:

0DCBA987     GetLongPathNameW       kernel32.dll

甚至只是

0DCBA987     GetLongPathNameW       ???

假设上述内容call GetProcAddress位于 address 0DCBA987

对我来说的优势是可读性。但它也会产生更全面的导入列表(以及外部参照),因为某些功能由于它们在各种 Windows 版本中的可用性而经常动态导入。

给定某个二进制文件,找出检索导入函数地址(例如GetProcAddress)的候选函数的所有外部参照,然后遍历它们的调用以查找导入了哪个函数,这应该是非常简单的DLL 部分可能更难找到,但可以留空或手动输入。但是我没有找到允许我添加导入的函数。有办法吗?

3个回答

不知道有没有很多人知道这一点,但是IDA使用了某种技巧来判断一个段是否是一个导入段,并且处理起来完全不同。

IDA 使用一些段属性将段视为导入段。例如,命名一个段.idata,或者将段的类设置为XTRN将立即使其成为导入段。这些段不会显示通常的代码/数据列表、函数定义和我们在代码视图中习惯的大多数其他内容。

更具体地说,向这些段添加代码将隐藏在 IDA 中,这将拒绝显示这些段中的任何程序集。我会说一个很好的反 IDA 技巧:)

相反,它们只会显示名称定义、偏移量和注释。一旦偏移量被分配了存储在 IDA 数据库中的 API 的名称,IDA 将获取 API 的原型和其他信息,分配类型定义和注释。

IIRC 这些 API 也将显示在导入窗口中,但我不确定究竟是什么触发了这一点。它还可能取决于 IDA 版本和其他与导入相关的 PE 属性。

编辑
已经三年多了,显然当时我找不到脚本,我只是查看了我的一些旧代码并能够重建我在该脚本中所做的事情,这里是它的要点:

import idaapi
import idc

for import_rva, import_name in LIST_OF_IMPORTS:
    ea = imagebase + import_rva

    if idaapi.get_segm_class(getseg(ea)) == "XTRN":
        print("import is already inside an XTRN segment, "
              "assuming it's correctly named")
        continue
    elif idaapi.get_segm_class(getseg(ea-1)) == "XTRN":
        print("Import is just below an import segment, "
              "extending segment to include this additional import")
        # shrinking it's current segment
        # WARNING: this assumes current import is at the top of its segment
        # otherwise we'll have to SPLIT the import's current segment
        # and to that I say CBA aka left as an exercise to the reader
        idaapi.set_segm_start(ea, SegStart(ea)+4, 0)
        # expanding it's new segment
        idaapi.set_segm_end(ea-1, SegEnd(ea-1)+4, 0)
    else:
        print("Creating new segment for import")
        idc.AddSeg(ea, ea+4, 0, 1, 4, 0)
        idc.SetSegClass(ea, "XTRN")

    # renaming import to API name. This will make IDA add type
    # information and automatic comments for any function it's
    # familiar with
    idc.MakeName(ea, import_name)
    # Making it an offset to have IDA show it as an import instead
    # of hiding it
    idc.MakeDword(ea)

如果向 PE 文件添加额外的导入部分是可以接受的选项

使用 iidking 之类的工具并添加一个导入部分,其中包含所有动态解析的导入

使用添加交叉引用对话框或 idc add_dref() 向它们添加交叉引用

演示代码

#include <stdio.h>
#include <windows.h>
#pragma comment(lib , "user32.lib")
DWORD (WINAPI * MyGetShortPathName)(LPCTSTR,LPTSTR,DWORD);
int main (void) {
    MessageBox(NULL,"testing add import" , "Test", MB_OK);
    char modname[MAX_PATH] = {0};
    GetModuleFileName(NULL,modname,MAX_PATH);
    printf("%s\n",modname);
    HMODULE hMod = LoadLibrary("kernel32.dll");
    if(hMod) {
        *(FARPROC *)&MyGetShortPathName = GetProcAddress(hMod,"GetShortPathNameA");
        if(MyGetShortPathName) {
            MyGetShortPathName(modname,modname,MAX_PATH);
            printf("%s\n",modname);
        }
    }
    return 0;
}

编译并执行

C:\codesnips\addimp\addimp.exe
C:\CODESN~1\addimp\addimp.exe

未修改的进口

00412000  GetCurrentThread                      KERNEL32 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
00412130  MessageBoxA                           USER32   

使用 iidking 修改的 exe 和在额外导入部分中添加的导入 GetShortPathNameA

C:\codesnips\addimp>fc /b addimp.exe modaddimp.exe
Comparing files addimp.exe and MODADDIMP.EXE
000000E6: 04 05  no of section 
00000131: 90 A0  
00000160: F4 00
00000161: 47 90
00000164: 3C 50
00000278: 00 2E .
00000279: 00 49 I
0000027A: 00 49 I
0000027B: 00 44 D
0000027C: 00 4B K
0000027D: 00 69 I
0000027E: 00 6E N
0000027F: 00 67 G
00000281: 00 02 vsize
00000285: 00 90 
00000286: 00 01
00000289: 00 02
0000028D: 00 5E
0000028E: 00 01
0000029C: 00 20
0000029F: 00 E0

ida导入修改后的exe窗口复制粘贴

00412000  GetCurrentThread                      KERNEL32 
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
00412130  MessageBoxA                           USER32   
00419058  GetShortPathNameA                     kernel32 

双击 419058 -> 打开视图 -> 打开子视图 -> 交叉引用 -> 添加交叉引用

Up P sub_401000+89 call    ds:GetProcAddress  (data xref)

或 idc 命令

add_dref(0x401089,0x419058,53);
AddCodeXref(0x419058,0x401089,53);

添加进口

将这三行编译成插件会将 Desired 导入添加到第一个模块的导入中

void idaapi run(int) {
nodeidx_t index = import_node.alt1st();
unsigned long value = import_node.altval(index);
netnode(value).supset(0x410004,"GetShortPathNameA\0");
}

用于创建 Segment 的 idc 文件

#include <idc.idc>
static main()
{
auto addr;
addr = 0x410000;
SegCreate(addr,addr+0x200,0,1,3,2);
SetSegmentType(addr,SEG_XTRN);
MakeDword(addr+4);
MakeName(addr+4,"GetShortPathNameA");
}

打开一个 exe / 运行 idc / 插件 / 关闭并保存数据库 / 重新打开数据库以查看第一个模块的导入选项卡中添加的导入

编辑
窗口 -> 重置桌面的工作方式与关闭和打开数据库的方式相同,从而避免关闭和打开数据库

如果您无法向导入查看器添加某些内容,则可以编写自己的内容。这是一个简单的示例(这是在此 hexblog 条目中引用的稍微修改的示例,位于此处,添加了双击功能、添加了列、删除了导出并修复了导入函数来源不明的情况下的错误)。请参阅函数 BuildImports 以创建额外的导入(manual_func1 和 manual_func2)

import idaapi
import idautils
from idaapi import PluginForm
from PySide import QtGui, QtCore

class ImpExpForm_t(PluginForm):

    def imports_names_cb(self, ea, name, ord):
        self.items.append((ea, '' if not name else name, ord))
        # True -> Continue enumeration
        return True

    def BuildImports(self):
        tree = {}
        nimps = idaapi.get_import_module_qty()

        for i in xrange(0, nimps):
            name = idaapi.get_import_module_name(i)
            if not name:
                name = "unknown"
            # Create a list for imported names
            self.items = []

            # Enum imported entries in this module
            idaapi.enum_import_names(i, self.imports_names_cb)

            if name not in tree:
                tree[name] = []
            tree[name].extend(self.items)
        tree["manually_added"] = [(0x01, "manual_func1", 3), (0x02, "manual_func2",4)]

        return tree

    def PopulateTree(self):
        # Clear previous items
        self.tree.clear()

        # Build imports
        root = QtGui.QTreeWidgetItem(self.tree)
        root.setText(0, "Imports")

        for dll_name, imp_entries in self.BuildImports().items():
            imp_dll = QtGui.QTreeWidgetItem(root)
            imp_dll.setText(0, dll_name)

            for imp_ea, imp_name, imp_ord in imp_entries:
                item = QtGui.QTreeWidgetItem(imp_dll)
                item.setText(0, "%s" % imp_name)
                item.setText(1, "0x%08x" % imp_ea)
                item.setText(2, "0x%08x" % imp_ord)

    def dblclick(self, item):
        try:
            idaapi.jumpto(int(item.text(1).encode("ascii", "ignore"), 16))
        except:
            print "Can not jump"


    def OnCreate(self, form):
        """
        Called when the plugin form is created
        """
        # Get parent widget
        self.parent = self.FormToPySideWidget(form)

        # Create tree control
        self.tree = QtGui.QTreeWidget()
        self.tree.setColumnCount(4)
        self.tree.setHeaderLabels(("Names","Address", "Ordinal", "Source"))
        self.tree.itemDoubleClicked.connect(self.dblclick)
        self.tree.setColumnWidth(0, 100)

        # Create layout
        layout = QtGui.QVBoxLayout()
        layout.addWidget(self.tree)

        self.PopulateTree()
        # Populate PluginForm
        self.parent.setLayout(layout)


    def OnClose(self, form):
        """
        Called when the plugin form is closed
        """
        global ImpExpForm
        del ImpExpForm
        print "Closed"


    def Show(self):
        """Creates the form is not created or focuses it if it was"""
        return PluginForm.Show(self,
                               "Imports / Exports viewer",
                               options = PluginForm.FORM_PERSIST)

# --------------------------------------------------------------------------
def main():
    global ImpExpForm

    try:
        ImpExpForm
    except:
        ImpExpForm = ImpExpForm_t()

    ImpExpForm.Show()

# --------------------------------------------------------------------------
main()