在使用反编译器时,有时您可能会在上下文菜单中看到名为拆分表达式
的项目。
它的作用是什么,在哪些情况下有用呢?让我们来看两个可以应用的例子。
结构字段初始化
现代编译器执行许多优化以加快代码执行速度。其中之一是将两个或多个相邻的内存存储或加载合并为一个宽的操作。这通常发生在写入相邻结构字段时。
例如,当您反编译一个使用块的macOS程序,并使用Objective-C分析插件来分析函数中的支持代码时,您可能会观察到类似于以下的伪代码:
block.isa = _NSConcreteStackBlock;
*(_QWORD *)&block.flags = 3254779904LL;
block.invoke = sub_10000A159;
block.descriptor = &stru_10001E0E8;
block.lvar1 = self;
block
变量使用了插件创建的结构,如下所示:
struct Block_layout_10000A088
void *isa;
int32_t flags;
int32_t reserved;
void (__cdecl *invoke)(Block_layout_10000A088 *block);
Block_descriptor_1 *descriptor;
_QWORD lvar1;
如您所见,编译器决定使用一个64位存储一次性初始化两个32位的flags
和reserved
字段。虽然技术上是正确的,但伪代码看起来有些难看,不易一目了然。要告诉反编译器这个写入应该被视为两个独立的操作,右键单击赋值并选择“拆分表达式”:
一旦伪代码刷新,两个独立的赋值将显示出来:
block.isa = _NSConcreteStackBlock;
block.flags = 0xC2000000;
block.reserved = 0;
block.invoke = sub_10000A159;
block.descriptor = &stru_10001E0E8;
block.lvar1 = self;
新的32位常量可以,例如,通过自定义枚举转换为十六进制或一组标志。
这个例子相对无害,因为reserved
字段被设置为0,所以常量实际上已经是32位;其他情况可能更复杂,当不同的独立值合并为一个大常量时。
如果需要,表达式可以进一步拆分(例如,当一个值用于初始化3个或更多字段时)。您也可以通过选择上下文菜单中的“取消拆分表达式”来恢复拆分。
32位程序中的64位变量
在处理具有32位寄存器的处理器上的64位值时,编译器必须以32位片段处理数据。
如果按原样翻译,这可能导致非常冗长的代码,因此我们的反编译器检测到常见模式,如64位数学运算、比较或数据操作,并自动创建由两个32位寄存器或内存位置组成的64位变量。虽然我们的启发式方法在大多数情况下效果良好,但可能会有误报,当两个实际上独立的32位变量合并为一个64位变量时。
在这种情况下,您可以对涉及该变量的64位操作使用“拆分表达式”来拆分对并恢复正确的独立变量。
更多文章
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA 技巧(69) 拆分表达式
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论