快速重命名在处理复制数据的代码时非常有用,因为变量名保持相同或相似。
然而,有时可以完全消除重复变量。
重复变量的原因
即使在源代码中一个特定变量可能只出现一次,但在机器代码层面并不总是如此。例如,大多数算术操作使用机器寄存器,因此必须将值从内存移动到寄存器以执行它们。相反,有时必须将值从寄存器移动到内存,例如:
-
获取变量的引用/地址需要它驻留在内存中; -
当可用寄存器太少时,一些变量必须被溢出到栈中; -
当调用约定使用栈来传递参数时; -
递归调用或闭包通常通过将当前变量存储在栈上来实现; -
其他一些情况。
所有这些意味着在函数的生命周期中,同一个变量可能出现在不同的位置。尽管反编译器尽力将这些不同位置合并为一个变量,但并不总是可能,因此在伪代码中可能会出现额外的变量。
示例
一个简单的例子,我们可以回到上一篇文章中的kprocesshacker.sys
中的驱动入口
。初始输出如下:
NTSTATUS __stdcall DriverEntry(_DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS result; // eax
NTSTATUS v5; // r11d
PDEVICE_OBJECT v6; // rax
struct _UNICODE_STRING DestinationString; // [rsp+40h] [rbp-18h] BYREF
PDEVICE_OBJECT DeviceObject; // [rsp+60h] [rbp+8h] BYREF
qword_132C0 = (__int64)DriverObject;
VersionInformation.dwOSVersionInfoSize = 284;
result = RtlGetVersion(&VersionInformation);
if ( result >= 0 )
{
result = sub_15100(RegistryPath);
if ( result >= 0 )
{
RtlInitUnicodeString(&DestinationString, L"\Device\KProcessHacker3");
result = IoCreateDevice(DriverObject, 0, &DestinationString, 0x22u, 0x100u, 0, &DeviceObject);
v5 = result;
if ( result >= 0 )
{
v6 = DeviceObject;
DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)&sub_11008;
qword_132D0 = (__int64)v6;
DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)&sub_1114C;
DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)&sub_11198;
DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_150EC;
v6->Flags &= ~0x80u;
return v5;
}
}
}
return result;
}
我们可以看到有两个看起来多余的变量:v5
和v6
。v5
是result
的副本,驻留在r11d
中,而v6
是DeviceObject
的副本,驻留在rax
中。它们似乎是出于相关原因引入的:
-
编译器必须将 DeviceObject
从栈移动到寄存器以初始化全局变量qword_132D0并修改Flags
成员。它选择了寄存器rax
; -
因为 rax
已经包含了result
变量(在它的低位部分:eax
),所以必须在此期间将其保存到其他地方(并在对DeviceObject
的操作结束时移回eax
); -
反编译器无法自动将 DeviceObject
与v6
合并,因为它们使用不同的存储类型(栈与寄存器),并且因为理论上对DriverObject->MajorFunction
的写入可能已经改变了栈变量,所以值可能不再相同。
映射变量
仔细查看代码后,似乎v5
和v6
可以分别被result
和DeviceObject
替换。在所有情况下都可以使用“映射到另一个变量”操作来让反编译器执行此操作。
当您第一次使用它时,会出现以下警告:
或者,您可以使用快捷键=
(等号);最好在初始赋值时使用它,例如v6 = DeviceObject
,因为这样在替换候选列表中会预先选择最佳匹配(赋值的另一侧)。在我们的例子中,我们只有一个候选,但在大型函数中,您可能有多个相同类型的变量,因此在赋值上触发操作有助于确保您选择正确的一个。
在映射了两个变量后,输出不再提及它们:
NTSTATUS __stdcall DriverEntry(_DRIVER_OBJECT *DriverObject, PUNICODE_STRING RegistryPath)
{
NTSTATUS result; // eax MAPDST
struct _UNICODE_STRING DestinationString; // [rsp+40h] [rbp-18h] BYREF
PDEVICE_OBJECT DeviceObject; // [rsp+60h] [rbp+8h] MAPDST BYREF
qword_132C0 = (__int64)DriverObject;
VersionInformation.dwOSVersionInfoSize = 284;
result = RtlGetVersion(&VersionInformation);
if ( result >= 0 )
{
result = sub_15100(RegistryPath);
if ( result >= 0 )
{
RtlInitUnicodeString(&DestinationString, L"\Device\KProcessHacker3");
result = IoCreateDevice(DriverObject, 0, &DestinationString, 0x22u, 0x100u, 0, &DeviceObject);
if ( result >= 0 )
{
DriverObject->MajorFunction[0] = (PDRIVER_DISPATCH)&sub_11008;
qword_132D0 = (__int64)DeviceObject;
DriverObject->MajorFunction[2] = (PDRIVER_DISPATCH)&sub_1114C;
DriverObject->MajorFunction[14] = (PDRIVER_DISPATCH)&sub_11198;
DriverObject->DriverUnload = (PDRIVER_UNLOAD)sub_150EC;
DeviceObject->Flags &= ~0x80u;
}
}
}
return result;
}
您可以看到result
和DeviceObject
变量现在有了一个新的注释:映射目标
。这意味着其他一些变量已被映射到它们。
取消映射变量
如果您改变了主意,想看看原始伪代码的样子,或者在输出中观察到涉及映射变量的可疑内容,可以通过右键单击一个映射变量(标记为映射目标
)并选择“取消映射变量”来移除映射。
更多文章
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA 技巧(77) 映射变量
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论