返回介绍

17.6 插件用户界面选项

发布于 2024-10-11 21:05:46 字数 16060 浏览 0 评论 0 收藏 0

本书并非用户界面开发指南,但许多时候,插件需要与 IDA 用户交互以请求或显示信息。除了第 16 章中提到的 askXXX 函数外,还有其他一些复杂的函数可以通过 IDA API 实现用户交互。对于更加大胆的插件作者来说,需要记住的是,为 GUI 版的 IDA 开发的插件也能够使用各种 GUI 库(Qt 或者 Windows Native)中的所有用户界面函数。通过使用这些函数,你几乎可以使用插件中的任何一种图形界面元素。

除 SDK 的 askXXX 界面函数外,使用 SDK 构建用户界面元素时,你将面临更大的挑战。其中一个原因是 SDK 试图通过提供一个非常通用的编程接口来完成向用户显示 GUI 元素和接受用户输入之类的复杂任务。

17.6.1 使用 SDK 的“选择器”对话框

我们首先讨论的两个函数是 choosechoose2 。kernwin.hpp 文件声明了这些函数以及用于控制其行为的各种常量。这两个函数的作用是向用户显示一组数据元素,并要求用户从中选择一项或几项。通过要求你指定格式化函数,从而生成在“选择器”窗口中显示的每一行文本, choose 函数几乎能够显示任何类型的数据。这两个函数的不同在于, choose 显示一个单列列表,而 choose2 则能够显示一个多列列表。下面的例子提供了这些函数的最简单代码,其中使用了许多默认参数。如果希望研究 choosechoose2 的全部功能,请参阅 kernwin.hpp 文件。

为向用户显示一列信息,最简单的 choose 函数代码如下所示,其中省略了一些默认参数:

ulong choose(void *obj,  
             int width,  
             ulong (idaapi *sizer)(void *obj),  
             char *(idaapi *getline)(void *obj, ulong n, char *buf),  
             const char *title);

在这个例子中, obj 参数是一个指向即将显示的数据块的指针, width 参数是“选择器”窗口所使用的列宽。 sizer 参数是一个指向某函数的指针,该函数能够解析 obj 所指的数据,并返回显示这些数据所需的行数。 getline 参数也是一个指向某函数的指针,该函数能够生成 obj 选择的一个项的字符串表示形式。值得注意的是,只要 sizer 函数能够解析相关数据来确定显示该数据所需的行数,且 getline 函数能够使用一个整数索引定位某数据项,并生成该数据项的字符串表示形式,则 obj 指针能够指向任何类型的数据。 title 参数指定生成的“选择器”对话框使用的标题字符串。 choose 函数返回用户选择的项目的索引(1.. n ),如果用户取消该对话框,则返回 0。代码清单 17-2 中的代码摘自某插件,虽然并不十分令人兴奋,但它说明了如何使用 choose 函数。

代码清单 17-2 choose 函数的示例用法

#include   

//The sample data to be displayed  
int data[] = {0xdeafbeef, 0xcafebabe, 0xfeedface, 0};  

//this example expects obj to point to a zero  
//terminated array of non-zero integers.  
ulong idaapi idabook_sizer(void *obj) {  
   int *p = (int*)obj;  
   int count = 0;  
   while (*p++) count++;  
   return count;  
}  

/*  
 * obj In this example obj is expected to point to an array of integers  
 * n indicates which line (1..n) of the display is being formatted.  
 *   if n is zero, the header line is being requested.  
 * buf is a pointer to the output buffer for the formatted data. IDA will  
 *     call this with a buffer of size MAXSTR (1024).  
 */  
char * idaapi idabook_getline(void *obj, ulong n, char *buf) {  
   int *p = (int*)obj;  
   if (n == 0) { //This is the header case  
      qstrncpy(buf, "Value", strlen("Value") + 1);  
   }  
   else { //This is the data case  
      qsnprintf(buf, 32, "0x%08.8x", p[n - 1]);  
   }  
   return buf;  
}  

void idaapi run(int arg) {  
   int choice = choose(data, 16, idabook_sizer, idabook_getline,  
                      "Idabook Choose");  
   msg("The user's choice was %d\n", choice);  
}

激活代码清单 17-2 中的插件将生成如图 17-4 所示的“选择器”对话框。

enter image description here

图 17-4 选择器对话框示例

choose2 函数可以显示多列形式的“选择器”对话框。同样,我们分析这个函数的最简单版本,接受所有可能的默认参数,如下所示:

ulong choose2(void *obj,  
              int ncol,  
              const int *widths,  
              ulong (idaapi *sizer)(void *obj),  
              void (idaapi *getline)(void *obj, ulong n, char* const *cells),  
              const char *title);

可以看到, choose2 函数与前面提到的 choose 函数有一些不同。首先, ncol 参数指定将要显示的列数,而 widths 参数是一个指定每列宽度的整数数组。在 choose2 中, getline 函数的格式发生了一些变化。由于 choose2 对话框能够显示多列, getline 函数必须为一行中的每列提供数据。代码清单 17-3 中的示例代码说明了 choose2 在一个示例插件中的用法。

代码清单 17-3  choose2 函数的用法

#include   

//The sample data to be displayed  
int data[] = {0xdeafbeef, 0xcafebabe, 0xfeedface, 0};  
//The width of each column  
int widths[] = {16, 16, 16};  
//The headers for each column  
char *headers[] = {"Decimal", "Hexadecimal", "Octal"};  
//The format strings for each column  
char *formats[] = {"%d", "0x%x", "0%o"};  

//this function expects obj to point to a zero terminated array  
//of non-zero integers.  
ulong idaapi idabook_sizer(void *obj) {  
   int *p = (int*)obj;  
   int count = 0;  
   while (*p++) count++;  
   return count;  
}  

/*  
 * obj In this function obj is expected to point to an array of integers  
 * n indicates which line (1..n) of the display is being formatted.  
 *   if n is zero, the header line is being requested.  
 * cells is a pointer to an array of character pointers. This array  
 *       contains one pointer for each column in the chooser.  The output  
 *       for each column should not exceed MAXSTR (1024) characters.*/  
void idaapi idabook_getline_2(void *obj, ulong n, char* const *cells) {  
   int *p = (int*)obj;  
   if (n == 0) {  
      for (int i = 0; i  3; i++) {  
         qstrncpy(cells[i], headers[i], widths[i]);  
      }  
   }  
   else {  
      for (int i = 0; i  3; i++) {  
         qsnprintf(cells[i], widths[i], formats[i], p[n - 1]);  
      }  
   }  
}  

void run(int arg) {  
   int choice = choose2(data, 3, widths, idabook_sizer, idabook_getline_2,  
                        "Idabook Choose2");  
   msg("The choice was %d\n", choice);  
}

使用代码清单 17-3 中的代码生成的多列“选择器”对话框如图 17-5 所示。

enter image description here

图 17-5  choose2 对话框示例

还可以通过 choosechoose2 函数实现更加复杂的用法。每个函数都可以创建模式对话框1 和非模式对话框,每个函数都能够生成允许选择多个项目的对话框。而且,这两个函数还接受其他几个参数,你能得知在对话框中发生的各种事件。如果使用这些函数创建非模式对话框,你将得到一个新的标签式窗口,它的标签将添加到其他 IDA 显示窗口(如 Imports 窗口)的标签旁边。实际上,IDA 的 Imports 窗口使用 choose2 界面实现。有关 choosechoose2 功能的更多信息,请参阅 kernwin.hpp 文件。

1. 你必须关闭模式对话框,才能继续与该对话框的父应用程序交互。“打开文件”和“保存文件”对话框就是典型的模式对话框。通常,在继续运行之前,如果应用程序需要用户提供信息,就会用到模式对话框。另一方面,非模式或无模式对话框可让用户在打开对话框的同时继续与父应用程序交互。

17.6.2 使用 SDK 创建自定义表单

SDK 还提供了 AskUsingForm_c 函数,用于创建更加复杂的用户界面元素。这个函数的原型如下所示:

int AskUsingForm_c(const char *form,...);

这个函数看似非常简单,却是 SDK 中最为复杂的用户界面函数之一。这种复杂性源于 form 参数,它用于指定自定义对话框中各种用户界面元素的布局。 form 参数本质上是一个描述各种输入元素布局的格式字符串,所以 AskUsingForm_cprintf 类似。 printf 格式字符串利用被格式化数据替代的输出格式符,而 AskUsingForm_c 格式字符串则由输出说明符和表单字段说明符组成,在显示表单时,后者由输入元素实例替代。与 printf 相比, AskUsingForm_c 使用一组截然不同的输出字段说明符。kernwin.hpp 及说明 AskUsingForm_c 用法的所有文档都详细介绍了这些说明符。表单字段说明符的基本格式如下所示:

〈#hint text#label:type:width:swidth:@hlp[]〉

下面介绍表单字段说明符中的每一个组件。

  • #hint text# 。这个元素可选。如果选择这个元素,当把光标悬停在相关输入字段上面时,提示文本(不包括#字符)将以工具提示的形式显示。

  • label 。作为标签在相关输入字段左侧显示的静态文本。对于按钮字段,它是按钮文本。

  • type 。一个字符,说明被指定的表单字段的类型。后面将介绍表单字段类型。

  • width 。相关输入字段接受的最大输入字符数。对于按钮字段,这个字段指定一个整数按钮识别码,用于区分不同的按钮。

  • swidth 。输入字段的显示宽度。

  • @hlp[] 。在 kernwin.hpp 文件中,这个字段被描述为“IDA.HLP 文件提供的帮助窗口的数量”。由于这个文件的内容由 Hex-Rays 指定,因此,绝大部分情况下,这个字段都没有多大用处。我们用一个冒号代替这个字段,表示忽略它。

在运行时实现对话框时将生成哪些类型的输入字段,取决于 type 字段所使用的字符。每种类型的表单字段都需要 AskUsingForm_c 参数列表的可变参数部分中的一个参数。表单字段类型说明符及其相关的参数类型如下所示(摘自 kernwin.hpp 文件)。

  Input field types                       va_list parameter
  -----------------                       -----------------
  A - ascii string                        char* at least MAXSTR size
  S - segment                             sel_t*
  N - hex number, C notation              uval_t*
  n - signed hex number, C notation       sval_t*
  L - default base (usually hex) number,  ulonglong*
      C notation
  l - default base (usually hex) number,  longlong*
      signed C notation
  M - hex number, no "0x" prefix          uval_t*
  D - decimal number                      sval_t*
  O - octal number, C notation            sval_t*
  Y - binary number, "0b" prefix          sval_t*
  H - char value, C notation              sval_t*
  $ - address                             ea_t*
  I - ident                               char* at least MAXNAMELEN size
  B - button                              formcb_t button callback function
  K - color button                        bgcolor_t*
  C - checkbox                            ushort* bit mask of checked boxes
  R - radiobutton                         ushort* number of selected radiobutton

所有数字字段将用户提交的输入解释成一个 IDC 表达式,当用户单击对话框的 OK 按钮时,IDA 将解析这个表达式并估算它的值。所有字段都需要一个用于输入和输出的指针参数。第一次生成表单时,所有表单字段的初值通过取消相关指针的引用获得。返回后,用户提交的表单字段值被写入到相关内存位置。与按钮(B)字段关联的指针参数是在按下该按钮时被调用的函数的地址。 formcb_t 函数的定义如下:

// callback for buttons  
typedef void (idaapi *formcb_t)(TView *fields[],int code);

这个按钮回调函数的 code 参数表示与被单击的按钮关联的代码(宽度)值。通过一个 switch 语句测试这段代码,你可以使用一个函数处理许多不同的按钮。

指定单选按钮和复选框的语法与其他类型的表单字段的格式略有不同。这些字段使用的格式如下所示:

<#item hint#label:type>

要对单选按钮和复选框分组,可以按顺序列出它们的说明符,并使用下面的特殊格式(注意末尾的另一个>)表示列表的结尾部分。

#item hint#label:type

你可以将一个单选按钮(或复选框)组用框框住,以突出显示。在指定组中的第一个元素时,你可以通过一个特殊的格式为框提供标题,如下所示:

#item hint#title#box hint#label:type

如果想要一个框标题,但不需要任何提示,可以省略提示,最终的格式说明符如下所示:

<##title##label:type>

现在看一下使用 AskUsingForm_c 创建对话框的一个例子。我们在这整个例子中使用的对话框如图 17-6 所示。

enter image description here

图 17-6 AskUsingForm_c 示例对话框

用于创建 AskUsingForm_c 对话框的格式字符串由许多代码行组成,它们指定该对话框的每一个元素。除了表单字段说明符以外,格式字符串可能还包含在生成的对话框中逐字显示的静态文本。此外,格式字符串还包含一个对话框标题(后面必须带两个换行符)、一个或几个行为指令(如 STARTITEM ,它指定对话框第一次显示时最初处于活动状态的表单字段的索引)。用于创建图 17-6 所示对话框的格式字符串如下所示:

char *dialog =  
 "STARTITEM 0\n"          //The first item gets the input focus  
 "This is the title\n\n"  //followed by 2 new lines  
 "This is static text\n"  
 "&ltString:A:32:32::>\n"   //An ASCII input field, need char[MAXSTR]  
 "&ltDecimal:D:10:10::>\n"  //A decimal input field, sval_t*  
 "&lt#No leading 0x#Hex:M:8:10::>\n"  //A Hex input field with hint, uval_t*  
 "&ltButton:B::::>\n"                 //A button field with no code, formcb_t  
 "&lt##Radio Buttons##Radio 1:R>\n"   //A radio button with box title  
 "&ltRadio 2:R>>\n"                   //Last radio button in group  
                                    //ushort* number of selected radio  
 "&lt##Check Boxes##Check 1:C>\n"     //A checkbox field with a box title  
 "&ltCheck 2:C>>\n";                  //Last checkbox in group  
                                    //ushort* bitmask of checks

通过格式化对话框元素,每行一个元素,可以将每个字段说明符与它们在图 17-6 中对应的字段轻松地对应起来。你可能注意到,在图 17-6 中,所有文本和数字输入字段均以下拉列表的形式出现。为了帮助你节省时间,IDA 用最近输入的值(其类型与相关输入字段的类型相匹配)填写了每个列表。下面的插件代码可用于显示上面的示例对话框并处理任何结果:

void idaapi button_func(TView *fields[], int code) {  
   msg("The button was pressed!\n");  
}  
void idaapi run(int arg) {  
   char input[MAXSTR];  
   sval_t dec = 0;  
   uval_t hex = 0xdeadbeef;  
   ushort radio = 1;      //select button 1 initially  
   ushort checkmask = 3;  //select both checkboxes initially  
   qstrncpy(input, "initial value", sizeof(input));  
   if (AskUsingForm_c(dialog, input, &dec, &hex,  
                      button_func, &radio, &checkmask) == 1) {  
      msg("The input string was: %s\n", input);  
      msg("Decimal: %d, Hex %x\n", dec, hex);  
      msg("Radio button %d is selected\n", radio);  
      for (int n = 0; checkmask; n++) {  
         if (checkmask & 1) {  
            msg("Checkbox %d is checked\n", n);  
         }  
         checkmask >>= 1;  
      }  
   }  
}

注意,在处理单选按钮和复选框结果时,每组中的第一个按钮被视为“按钮 0”。

AskUsingForm_c 函数有相当强大的功能,可用于为你的插件设计用户界面元素。这里的例子只是粗略介绍了这个函数的许多功能,kernwin.hpp 文件详细介绍了更多其他功能。请参阅这个文件,了解有关 AskUsingForm_c 函数及其功能的详细信息。

17.6.3 仅用于 Windows 的用户界面生成技巧

许多开发者一直在全力应付为插件创建用户界面的难题。针对 IDA 的 Windows GUI 版本(idag.exe )的插件可以使用所有 Windows 图形 API。Tenable Security 的 mIDA2 插件的作者设计了另一种方法,可用于创建 mIDA 插件使用的 MDI3 客户窗口。为解决 mIDA 开发者面临的挑战,IDA 支持论坛提供了一个超长的线程4 。该线程还包含示例代码,说明他们的解决方案。

2. 参见 http://cgi.tenablesecurity.com/tenable/mida.php
3. Windows 多文档界面(MDI )允许在一个容器窗口中包含多个子(客户)窗口。
4. 参见 http://www.hex-rays.com/forum/viewtopic.php?f=8&t=1660&p=6752

ida-x86emu5 插件使用的用户界面与其他插件稍有不同。这个插件使用下面的 SDK 代码获得 IDA 主窗口的一个句柄:

5. 参见 http://www.idabook.com/ida-x86emu

HWND mainWindow = (HWND)callui(ui_get_hwnd).vptr;

ida-x86emu 现在并没有整合到 IDA 工作区中,只是使用 IDA 主窗口作为父窗口。这个插件的所有对话框界面全都使用 Windows 资源编辑器生成,所有用户交互则通过直接调用 Windows API 函数来处理。使用图形对话框编辑器并直接调用本地 Windows API 函数,可以实现最强大的用户界面生成功能,但这种方法非常复杂,而且需要使用者了解其他一些知识,如处理 Windows 消息以及使用低级的界面函数。

17.6.4 使用 Qt 生成用户界面

IDA 6.0 中引入的 Qt 用户界面为插件开发者创建具有复杂用户界面、可用于所有 IDA 平台的插件提供了机会。Hex-Rays 的 Daniel Pistelli6 在 Hex-Rays 博客上的一篇文章中讨论了在插件中使用 Qt 的一些要求。7 在这一节中,我们将重申 Daniel 提出的一些要点,并提供其他一些有用信息。

6. Daniel 负责 Hex-Rays 将 IDA 的 GUI 迁移到 Qt 的工作。
7. 参见 http://www.hexblog.com/?p=250

如果你希望在插件中利用 Qt 的任何功能,首先你必须正确配置 Qt 开发环境。IDA 6.1 附带有自己的 Qt 4.7.2 库8 。Hex-Rays 建立自己的 Qt 库时,它将该库包装在一个名为 QT 的 C++ 命名空间中。要配置你的开发环境,请从 Nokia 获取适当的 Qt 源代码。Windows 版本的 idaq 使用 Visual Studio 2008 创建9 ,Linux 和 OS X 版本的则使用 g++ 创建。请从以下地址下载 Windows 版本的源代码:

8. IDA 6.0 使用 Qt 4.6.3。
9. 因此,如果要在 Windows 上创建 Qt 相关的插件,你必须使用 Visual Studio 来创建你的插件。

ftp://ftp.qt.nokia.com/qt/source/qt-win-opensource-4.7.2-vs2008.exe

请从以下地址下载 Linux 和 OS X 版本的源代码:

ftp://ftp.qt.nokia.com/qt/source/qt-everywhere-opensource-src-4.7.2.tar.gz  

请参阅 Daniel 的博客文章了解用于配置源代码的特定命令。正确配置的关键在于使用以下命令行参数:

-qtnamespace QT

此参数将 Qt 源代码包装在 QT 命名空间中。要在 Windows 上创建任何 Qt 相关插件,你需要将在插件中用到的每个 Qt 库的链接库(.lib 文件)。虽然 IDA 附带了大量 Qt 动态链接库(参见 目录了解完整列表),但 SDK 附带的用于 Windows 的 Qt 链接库的数量非常有限(主要包括 QtCore4 和 QtGui),这些库可以在/lib/x86_win_qt 目录中找到。如果你需要其他链接库,你需要链接到你自己从 Qt 源代码创建的库。在 Linux 和 OS X 上,你可以直接链接 IDA 附带的 Qt 库。在 Linux 上,这些库位于 目录中,而在 OS X 上,它们位于/idaq.app/Contents/Frameworks 目录中。请注意,链接并非由 IDA 附带的 Qt 库会降低插件的兼容性,除非你与插件一起发布这些库。

配置 Qt 插件项目时,请确保 qmake 项目文件包含以下项目配置指令:

QT_NAMESPACE = QT

IDA 定义了许多函数,以便于在 SDK 中更安全地处理字符串。这些函数包括 qstrlenqsnprintf ,很长时间以来,它们一直是 SDK 的一部分。迁移到基于 Qt 的 GUI 后,使用这些函数可能会导致问题,因为 Qt 还定义了几个与 IDA 所提供的函数名称相同的函数。IDA 函数位于全局命名空间中,而 Qt 函数则位于 QT 命名空间中。通过明确引用全局命名空间,可以调用这些函数的 IDA 版本,如下所示:

unsigned int len = ::qstrlen(myString);

如果你需要为你在插件中创建的任何部件提供一个父部件(parent widget),使用下面的语句将获得一个指向 idaq 的顶级应用程序窗口的指针:

QWidget *mainWindow = QApplication::activeWindow();

这个语句调用 Qt QApplication 类中的一个静态方法,并返回任何 Qt 应用程序中唯一的 QApplication 对象的部件指针。

有关如何配置插件以使用 Qt 的详细信息,请参阅 Daniel 的博客文章。此外,IDA SDK 附带的 qwindow 插件样本也提供了一个使用 Qt 的插件示例。具体而言,其中包含示例代码,它用于创建一个空部件(使用 create_tform ),使用回调以接收正显示表单的通知,获得指向新建表单的 QWidget 指针,以及最终使用一个 Qt 按钮对象填写该表单。将在第 23 章中讨论的 collabREate 和 ida-x86emu 插件也利用了 Qt GUI 元素,以将这些插件用在所有 IDA 平台中。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文