在IDA 7.6中新增的功能之一是反编译器中变量的自动重命名。
与PIT不同,它不仅限于堆栈变量,还处理存储在寄存器中的变量,不仅是调用,还包括赋值和其他一些表达式。它还尝试解释包含动词(如get、make、fetch、query等)的函数名,并相应地重命名分配的结果。
手动触发重命名
为了应对自动重命名失败或不足的情况,反编译器还支持一个名为“快速重命名”的手动操作,默认快捷键为Shift
–N
。它可以用于在赋值和其他表达式中传播名称。通常,它只重命名用户未明确命名的虚拟变量(如v1
、v2
等)。以下是该操作使用的一些不完整规则列表:
-
根据赋值中相对变量的名称: v1 = myvar
:重命名v1 -> myvar1 -
根据比较中相对变量的名称: offset < v1
:重命名v1 -> offset1 -
作为指向命名良好的变量的指针: v1 = &Value
:重命名v1 -> p_Value -
根据表达式中的结构字段: v1 = x.Next
:重命名v1 -> Next -
作为指向结构字段的指针: v1 = &x.left
:重命名v1 -> p_left -
根据调用中形式参数的名称: close(v1)
:重命名v1 -> fd -
根据被调用函数的名称: v1=create_table()
:重命名v1 -> table -
根据被调用函数的返回类型: v1 = strchr(s, '1')
:重命名v1 -> str -
根据字符串常量: v1 = fopen("/etc/fstab", "r")
:重命名v1 -> etc_fstab -
根据变量类型:error_t v1:重命名v1 -> error -
结果变量的标准名称: return v1
:如果当前函数返回布尔值,重命名v1 -> ok
示例:Windows驱动程序
我们将检查Process Hacker使用的驱动程序,以执行需要内核模式访问的操作。打开kprocesshacker.sys
时,IDA会自动将已知的函数原型应用于DriverEntry
入口点,并加载内核模式类型库,因此默认反编译已经相当不错:
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;
}
然而,为了理解它,我们需要进行一些更改。MajorFunction
数组的索引是所谓的IRP主要功能代码,它们有以IRP_MJ_
开头的符号名称。因此,我们可以应用枚举操作(M
快捷键)将数字转换为类型库中可用的相应符号常量。
之后,我们可以重命名相应的例程,使伪代码看起来非常类似于标准DriverEntry:
为了去除强制转换,使用“设置项目类型”操作(Y
快捷键)为调度例程设置正确的原型。我们可以为所有三个例程使用相同的原型字符串:
NTSTATUS Dispatch(PDEVICE_OBJECT Device, PIRP Irp)
这之所以有效,是因为函数名不被视为函数原型的一部分,并且被IDA忽略。对于卸载函数,原型不同:
void Unload(PDRIVER_OBJECT Driver)
设置原型后,不再有强制转换:
现在我们可以进入KhDispatchDeviceControl
来研究它是如何工作的。由于预设的原型,初始伪代码乍一看是合理的:
NTSTATUS __stdcall KhDispatchDeviceControl(PDEVICE_OBJECT Device, PIRP Irp)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v13 = Irp;
CurrentStackLocation = Irp->Tail.Overlay.CurrentStackLocation;
FsContext = CurrentStackLocation->FileObject->FsContext;
Parameters = CurrentStackLocation->Parameters.CreatePipe.Parameters;
Options = CurrentStackLocation->Parameters.Create.Options;
LowPart = CurrentStackLocation->Parameters.Read.ByteOffset.LowPart;
AccessMode = Irp->RequestorMode;
if ( !FsContext )
{
v9 = -1073741595;
goto LABEL_105;
}
if ( LowPart != -1718018045
&& LowPart != -1718018041
&& (dword_132CC == 2 || dword_132CC == 3)
&& (*FsContext & 2) == 0 )
但仔细检查后,一些奇怪之处变得明显。_IO_STACK_LOCATION结构的Parameters
成员是一个联合体,包含请求特定的参数。由于信息不足,反编译器选择了第一个匹配的成员,但它们对于我们正在处理的请求没有意义。对于IRP_MJ_DEVICE_CONTROL
,应使用DeviceIoControl
结构。
因此,我们可以使用“选择联合字段”操作(Alt
–Y
快捷键)在对CurrentStackLocation->Parameters
的三个引用上选择DeviceIoControl
,以查看实际使用了哪些参数。
引用已更改,但变量名称和类型保持不变。在这种情况下,我们可以通过在赋值上使用快速重命名(Shift
–N
)来更新名称。
为了去除强制转换,我们可以手动将Type3InputBuffer
变量类型更改为void*
,或者简单地刷新反编译(F5)。这会导致反编译器重新运行类型推导算法并更新自动类型化变量的类型。
现在伪代码更接近于实际情况。特别是,我们可以看到第一个比较正在检查IoControlCode
是否与一些预期值匹配,这比原始的LowPart
更有意义。
其他用途
当由于名称冲突导致自动重命名失败时,快速重命名可能会很有用。例如,如果我们回到DriverEntry
,可以看到DeviceObject
被复制到一个临时变量v6
:
v6 = DeviceObject;
DriverObject->MajorFunction[IRP_MJ_CREATE] = KhDispatchCreate;
qword_132D0 = (__int64)v6;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = KhDispatchClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = KhDispatchDeviceControl;
DriverObject->DriverUnload = KhUnload;
v6->Flags &= ~0x80u;
我们可以手动重命名v6
,或者简单地在赋值上按Shift
–N
,反编译器将重用名称并添加数字后缀以解决冲突:
DeviceObject1 = DeviceObject;
DriverObject->MajorFunction[IRP_MJ_CREATE] = KhDispatchCreate;
qword_132D0 = (__int64)DeviceObject1;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = KhDispatchClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = KhDispatchDeviceControl;
DriverObject->DriverUnload = KhUnload;
DeviceObject1->Flags &= ~0x80u;
更多文章
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA 技巧(76) 快速重命名
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论