返回介绍

15.2 IDC 语言

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

与 IDA 的其他功能不同,IDA 的帮助系统为 IDA 语言提供了诸多帮助。帮助系统中的主题大致包括 IDC 语言(介绍 IDC 语法基础)和 IDC 函数目录(详细说明可供 IDC 程序员使用的内置函数)。

IDC 脚本语言借用了 C 语言的许多语法。从 IDA5.6 开始,IDC 在面向对象特性和异常处理方面与 C++ 更为相似。由于 IDC 与 C 语言和 C++ 语言类似,因此,我们将依据这些语言来介绍 IDC 语言,并重点说明这两种语言之间的区别。

15.2.1 IDC 变量

IDC 是一种类型松散的语言,这表示它的变量没有明确的类型。IDC 使用 3 种数据类型:整数(IDA 文档使用类型名称 long )、字符串和浮点值,其中绝大部分的操作针对的是整数和字符串。字符串被视为 IDC 中的本地数据类型,因此,你不需要跟踪存储一个字符串所需的空间,或者一个字符串是否使用零终止符。从 IDA5.6 开始,IDC 加入了许多变量类型,包括对象、引用和函数指针。

在使用任何变量前,都必须先声明该变量。IDC 支持局部变量,并且从 IDA5.4 开始,也支持全局变量。IDC 关键字 auto 用于引入一个局部变量声明,并且局部变量声明中可能包括初始值。如下所示是合法与非法的 IDC 局部变量声明:

auto addr, reg, val;   // legal, multiple variables declared with no initializers  
auto count = 0;        // declaration with initialization

IDC 认可使用/* */的 C 风格多行注释,以及使用//的 C++ 风格行尾注释。此外,需要注意的是,你可以在一个语句中声明好几个变量,并且 IDC 中的所有语句均使用分号为终止符(和 C 语言中一样)。IDC 并不支持 C 风格数组(IDA 5.6 引入了分片)、指针(虽然 IDA 从 IDA 5.6 开始支持引用)或结构体和联合之类的复杂数据类型。IDA 5.6 引入了类的概念。

IDA 使用 extern 关键字引入全局变量声明,你可以在任何函数定义的内部和外部声明全局变量,但不能在声明全局变量时为其提供初始值。下面的代码清单声明了两个全局变量。

extern outsideGlobal;  

static main() {  
   extern insideGlobal;  
   outsideGlobal = "Global";  
   insideGlobal = 1;  
}

在 IDA 会话过程中首次遇到全局变量时,IDA 将对全局变量进行分配,只要该会话处于活动状态,无论你打开或关闭多少个数据库这些变量都将始终有效。

15.2.2 IDC 表达式

除少数几个特例外,IDC 几乎支持 C 中的所有算术和逻辑运算符,包括三元运算符(? :)。IDC 不支持 op= (+=、*=、>>= 等)形式的复合赋值运算符。从 IDA5.6 开始,IDC 开始支持逗号运算。所有整数操作数均作为有符号的值处理。这会影响到整数比较(始终带有符号)和右移位运算符(>>),因为它们总是会通过符号位复制进行算术移位。如果需要进行逻辑右移位,你必须修改结果的最高位,自己移位,如下所示:

result = (x >> 1) & 0x7fffffff;  //set most significant bit to zero

由于字符串是 IDC 中的本地类型,因此,IDC 中的一些字符串运算与 C 中的字符串运算有所不同。在 IDC 中,给字符串变量中的字符串操作数赋值将导致字符串复制操作,因此,你不需要使用字符串来复制函数,如 C 语言中的 strcpystrdup 函数。将两个字符串操作数相加会将这两个操作数拼接起来,因此“Hello”+“World”将得到“HelloWorld”。因此,你不需要使用如 C 语言中的 strcat 之类的拼接函数。从 IDA 5.6 开始,IDA 提供用于处理字符串的分片运算符(slice operator)。Python 程序员需要对分片有所了解。通常你可以通过分片指定与数组类似的变量的子序列。分片使用方括号和起始索引(包括)与结束索引(不包括)来指定(至少需要一个索引)。下面的代码清单说明了 IDC 分片的用法。

auto str = "String to slice";  
auto s1, s2, s3, s4;  
s1 = str[7:9];     // "to"  
s2 = str[:6];      // "String", omitting start index starts at 0  
s3 = str[10:];     // "slice", omitting end index goes to end of string  
s4 = str[5];       // "g", single element slice, similar to array element access

需要注意的是,虽然 IDC 中并没有数组数据类型,但你可以使用分片运算符来处理 IDC 字符串,就好像它们是数组一样。

15.2.3 IDC 语句

和 C 语言一样,IDC 中的所有简单语句均以分号结束。 switch 语句是 IDC 唯一不支持的 C 风格复合语句。在使用 for 循环时,需要记住的是,IDC 不支持复合赋值运算符,如果你希望以除 1 以外的其他值为单位进行计数,就需要注意这一点。如下所示:

auto i;  
for (i = 0; i < 10; i += 2) {}     // illegal, += is not supported  
for (i = 0; i < 10; i = i + 2) {}  // legal  

在 IDA 5.6 中,IDC 引入了 try/catch 块和相关的 throw 语句,在语法上它们类似于 C++ 异常1 。有关 IDC 异常处理的详细信息,请参阅 IDA 的内置帮助文件。

1. 参见 http://www.cplusplus.com/doc/tutorial/exceptions/

在复合语句中,IDC 使用和 C 语言一样的花括号语法和语义。在花括号中可以声明新的变量,只要变量声明位于花括号内的第一个语句即可。但是,IDC 并不严格限制新引入的变量的作用范围,因此,你可以从声明这些变量的花括号以外引用它们。请看下面的例子:

if (1) {    //always true  
   auto x;  
   x = 10;  
}  
else {      //never executes  
   auto y;  
   y = 3;  
}  
Message("x = %d\n", x);   // x remains accessible after its block terminates  
Message("y = %d\n", y);   // IDC allows this even though the else did not execute

输出语句( Message 函数类似于 C 语言的 printf 函数)告诉我们: x=10y=0 。由于 IDC 并不严格限制 x 的作用域,因此毫不奇怪,我们可以打印 x 的值。令人奇怪的是,我们还可以访问 y 值,而声明 y 的代码块从未执行。这只是 IDC 的一个古怪行为。值得注意的是,虽然 IDC 并不严格限制变量在函数中的作用域,但是,在一个函数中,你不能访问在其他任何函数中声明的变量。

15.2.4 IDC 函数

IDC 仅仅在独立程序(.idc 文件)中支持用户定义的函数。IDC 命令对话框(参见本节的“使用 IDC 命令对话框”)不支持用户定义的函数。IDC 用于声明用户定义的函数的语法与 C 语言差异甚大。在 IDC 中, static 关键字用于引入一个用户定义的函数,函数的参数列表仅包含一个以逗号分隔的参数名列表。下面详细说明了一个用户定义的函数的基本结构:

static my_func(x, y, z) {  
   //declare any local variables first  
   auto a, b, c;  
   //add statements to define the function's behavior  
   // ...  
}

在 IDA5.6 之前,所有函数参数都严格采用传值(call-by-value)传递,IDA5.6 引入了传地址(call-by-reference )参数传递机制。有趣的是,是采用传值(call-by-value)方式还是传地址(call-by-reference )方式传递参数,由 IDA 调用函数的方式而不是声明函数的方式决定。在函数调用( 而不是 函数声明)中使用一元运算符&说明该函数采用传地址方式传递参数。在下面的例子中,上一个代码清单中的 my_func 函数同时采用了这两种参数传递方式。

auto q = 0, r = 1, s = 2;  
my_func(q, r, s);  //all three arguments passed using call-by-value  
                   //upon return, q, r, and s hold 0, 1, and 2 respectively  
my_func(q, &r, s); //q and s passed call-by-value, r is passed call-by-reference  
                   //upon return, q, and s hold 0 and 2 respectively, but r may have  
                   //changed. In this second case, any changes that my_func makes to its
                   //formal parameter y will be reflected in the caller as changes to r

注意,一个函数声明绝不会指明该函数是否明确返回一个值,以及在不生成结果时,它返回什么类型的值。

使用 IDC 命令对话框

IDC 命令对话框提供一个简单的界面,可用于输入少量的 IDC 代码。使用命令对话框,不用创建独立的脚本文件,即可快速输入和测试新脚本。在使用命令对话框时,最重要的是,绝不能在对话框中定义任何函数。基本上,IDA 将你的语句包装在一个函数中,然后调用这个函数以执行语句。如果要在该对话框中定义一个函数,你将得到一个在函数中定义的函数。由于 IDC 不支持嵌套函数声明(C 语言也是如此),这样做会导致一个语法错误。

如果你希望函数返回一个值,可以使用 return 语句返回指定的值。你可通过函数的不同执行路径返回不同的数据类型。换言之,某些情况下,一个函数返回一个字符串;而在其他情况下,这个函数却返回一个整数。和 C 语言中一样,你不一定非要在函数中使用 return 语句。但是,任何不会显式返回一个值的函数将返回零值。

最后需要注意的是,从 IDA 5.6 开始,函数离成为 IDC 中的第一类对象更近了一步。现在,你可以将函数引用作为参数传递给另一个函数,并将函数引用作为函数的结果返回。下面的代码清单说明了使用函数参数和函数作为返回值的情况。

static getFunc() {  
  return Message;  //return the built-in Message function as a result  
}  

static useFunc(func, arg) {  //func here is expected to be a function reference  
  func(arg);  
}  

static main() {  
  auto f = getFunc();  
  f("Hello World\n");       //invoke the returned function f  
  useFunc(f, "Print me\n"); //no need for & operator, functions always call-by-reference  
}

15.2.5 IDC 对象

IDA 5.6 引入的另一项功能是能够定义类,并因此具有表示对象的变量。在下面的讨论中,我们假设你在一定程度上熟悉 C++ 或 Java 等面向对象的编程语言。

IDA 脚本发展

如果你并不清楚 IDA 5.6 已对 IDC 作出了大量更改,只能说明你的关注度还不够。在 IDA 5.4 中集成 IDAPython 后,Hex-Rays 致力于增强 IDC 的功能,因而在 IDA 5.6 中引入了本章提到的许多功能。在这个过程中,Hex-Rays 甚至考虑将 JavaScript 添加到 IDA 的脚本“阵容”中。*

* 请参见 http://www.hexblog.com/?p=101

IDC 定义了一个称为 object 的根类,最终所有类都由它衍生而来,并且在创建新类时支持单一继承。IDC 并不使用访问说明符,如 publicprivate 。所有类成员均为有效公共类。类声明仅包含类成员函数的定义。要在类中创建数据成员,你只需要创建一个给数据成员赋值的赋值语句即可。下面的代码清单有助于说明这一点。

class ExampleClass {  
   ExampleClass(x, y) {   //constructor  
      this.a = x;         //all ExampleClass objects have data member a  
      this.b = y;         //all ExampleClass objects have data member b  
   }  
   ~ExampleClass() {      //destructor  
   }  
   foo(x) {  
      this.a = this.a + x;  
   }  
   //...   other member functions as desired  
};  

static main() {  
   ExampleClass ex;           //DON’T DO THIS!! This is not a valid variable declaration  
   auto ex = ExampleClass(1, 2);  //reference variables are initialized by assigning   
                                  //the result of calling the class constructor  
   ex.foo(10);                //dot notation is used to access members  
   ex.z = "string";           //object ex now has a member z, BUT the class does not  
}

有关 IDC 类及其语法的更多信息,请参阅 IDA 内置帮助文件中的相应章节。

15.2.6 IDC 程序

如果一个脚本应用程序需要执行大量的 IDC 语句,你可能需要创建一个独立的 IDC 程序文件。另外,将脚本保存为程序,你的脚本将获得一定程度的持久性和可移植性。

IDC 程序文件要求你使用用户定义的函数。至少,必须定义一个没有参数的 main 函数。另外,主程序文件还必须包含 idc.idc 文件以获得它包含的有用宏定义。下面详细说明了一个简单的 IDC 程序文件的基本结构:

#include     // useful include directive  
//declare additional functions as required  
static main() {  
   //do something fun here  
}

IDC 认可以下 C 预处理指令。

  • #include<文件> 。将指定的文件包含在当前文件中。

  • #define< 宏名称>[ 可选值] 。创建一个宏,可以选择给它分配指定的值。IDC 预定义了许多宏来测试脚本执行环境。这些宏包括 _NT__LINUX__MAC__GUI__TXT_ 等。有关这些宏及其他符号的详细信息,请参阅 IDA 帮助文件的“预定义的符号”(Predefined symbols)部分。

  • #ifdef<名称> 。测试指定的宏是否存在,如果该宏存在,可以选择处理其后的任何语句。

  • #else 。可以与 #ifdef 指令一起使用,如果指定的宏不存在,它提供另一组供处理的语句。

  • #endif#ifdef#ifdef/#else 块所需的终止符。

  • #undef< 名称> 。删除指定的宏。

15.2.7 IDC 错误处理

没有人会因为 IDC 的错误报告功能而称赞 IDC 。在运行 IDC 脚本时,你可能遇到两种错误:解析错误和运行时错误。

解析错误 指那些令你的程序无法运行的错误,包括语法错误、引用未定义变量、函数参数数量错误。在解析阶段,IDC 仅报告它遇到的第一个解析错误。有时候,错误消息能够正确确定错误的位置和类型( hello_world.idc, 20: Missing semicolon )。而在有些情况下,错误消息并不能提供任何有用的信息( Syntax error near: <END> )。IDC 仅报告在解析过程中遇到的第一个错误。因此,如果一个脚本包含 15 个语法错误,在它向你报告每个错误之前,它会进行 15 次运行尝试。

通常,与解析错误相比,运行时错误(runtime error)较为少见。运行时错误会使一段脚本立即终止运行。例如,如果你试图调用一个未定义的函数(由于某种原因,在最初解析脚本时并没有发现这个问题),这时就会发生运行时错误。另外,如果一个脚本的运行时间过长,也会发生运行时错误。一旦脚本开始运行,如果它不慎进入一个无限循环,或者运行的时间超过你的预期,你就没有办法直接终止这个脚本。因此,如果一个脚本的运行时间超过 2 秒或 3 秒,IDA 将显示如图 15-4 所示的对话框。

enter image description here

图 15-4 取消脚本对话框

只有使用这个对话框,你才能终止一个无法正常终止的脚本。

调试是 IDC 的另一个缺陷。除了大量使用输出语句外,你没有办法调试 IDC 脚本。在 IDA 5.6 中引入异常处理( try/catch )之后,你就能够构建更加强大的、可根据你的需要终止或继续的脚本。

15.2.8 IDC 永久数据存储

如果你不相信我们会全面介绍 IDA 的脚本功能,可能会去查看 IDA 帮助系统中的相关主题。如果是这样,欢迎你以后再回来阅读本书。如果并非如此,感谢你对我们的信任。在学习 IDA 的过程中,你可能会得知 IDC 实际上并不支持数组。那时,你肯定会质疑本书的质量,现在我强烈要求你给我一个机会消除这个潜在的困惑。

如前所述,IDC 并不支持传统意义上的数组,即那种首先声明一个大型存储块,然后使用下标符号访问块中的每一个数据项的数组。但是,IDA 中有关脚本的文档确实提到“全局永久数组”(global persistent array)。用户最好是将 IDC 全局数组看成已命名的永久对象(persistent named object)。这些对象恰巧是稀疏数组(sparse array )2 。全局数组保存在 IDA 数据库中,对所有脚本调用和 IDA 会话永久有效。要将数据保存在全局数组中,你需要指定一个索引及一个保存在该索引位置的数据值。数组中的每个元素同时保存一个整数值和一个字符串值。IDC 的全局数组无法存储浮点值。

2. 稀疏数组不一定会预先给整个数组分配空间,也不仅限于使用某个特殊的最大索引。实际上,当元素添加到数组中时,它按需分配这些元素的空间。

说明  IDA 用于存储永久数组的内部机制叫做网络节点。虽然下面介绍的数组操纵函数提供了一个访问网络节点的抽象接口,但用户可以在 IDA SDK 中找到访问网络节点数据的低级方法。我们将在第 16 章讨论 IDA SDK 和网络节点。

与全局数组的所有交互通过专门用于操纵数组的 IDC 函数来完成。这些函数如下所示。

  • long CreateArray(string name) 。这个函数使用指定的名称创建一个永久对象。它的返回值是一个整数句柄,将来访问这个数组时,你需要这个句柄。如果已命名对象已经存在,则返回-1。

  • long GetArrayId(string name) 。创建一个数组后,随后要访问这个数组,必须通过一个整数句柄来实现,你可以通过查询数组名称获得这个句柄。这个函数的返回值是一个用于将来与该数组交互的整数句柄。如果已命名数组并不存在,则返回-1。

  • long SetArrayLong(long id, long idx, long value) 。将整数 value 存储到按 id 引用的数组中 idx 指定的位置。如果操作成功,则返回 1,否则返回 0。如果数组 id 无效,这个操作将会失败。

  • long SetArrayString(long id, long idx, string str) 。将字符串 value 存储到按 id 引用的数组中 idx 指定的位置。如果操作成功,则返回 1,否则返回 0。如果数组 id 无效,这个操作将会失败。

  • string or long GetArrayElement(long tag, long id, long idx) 。虽然一些特殊的函数可以根据数据类型将数据存储到数组中,但是,只有一个函数可以从数组中提取数据。这个函数可以从指定数组( id )的指定索引( idx )位置提取一个整数或字符串值。提取的是整数还是字符串,由 tag 参数的值决定,这个值必须是常量 AR_LONG (提取整数)或 AR_STR (提取字符串)。

  • long DelArrayElement(long tag, long id, long idx) 。从指定数组中删除指定数组位置的内容。 tag 的值决定是删除指定索引位置的整数值还是字符串值。

  • void DeleteArray(long id) 。删除按 id 引用的数组及其所有相关内容。创建一个数组后,即使一个脚本终止,它也继续存在,直到调用 DeleteArray 从创建它的数据库中删除这个数组。

  • long RenameArray(long id, string newname) 。将按 id 引用的数组重命名为 newname 。如果操作成功,将返回 1,否则返回 0。

全局数组的作用包括模拟全局变量、模拟复杂的数据类型、为所有脚本调用提供永久存储。在数组开始执行时创建一个全局数组,然后将全局值存储到这个数组中,即可模拟这个数组的全局变量。要共享这些全局值,可以将数组句柄传递给要求访问这些值的函数,或者要求请求访问这些值的函数对相关数组进行名称查询。

IDC 全局数组中存储的值会在执行脚本的数据库中永久存在。你可以通过检查 CreateArray 函数的返回值,测试一个数组是否存在。如果一个数组中存储的值仅适用于某个特定的脚本,那么,在这个脚本终止前,应该删除该数组。删除数组可以确保全局值不会由同一个脚本的上一次执行传递到随后的执行中。

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

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

发布评论

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