之前我们讨论过如何通过映射将变量的副本减少为一个,从而减少伪代码中使用的变量数量。然而,有时你可能会遇到相反的问题:一个变量可能会被用于不同的目的。
重用的栈槽
一个常见的情况是编译器重用了局部变量或甚至是传入栈参数的栈位置,用于不同的目的。例如,在这样的代码片段中:
vtbl = DiaSymbol->vtbl;vtbl->get_symTag(DiaSymbol, (int *)&DiaSymbol);Symbol->Tag = (int)DiaSymbol;
调用的第二个参数显然是一个输出参数,其含义和类型与调用前的DiaSymbol
不同。在这种情况下,你可以使用“强制新变量”命令(快捷键Shift–F)。由于实现细节,有时如果你右键点击变量本身,选项不会显示;在这种情况下,尝试右键点击伪代码行的开头。
反编译器在相同的栈位置创建了一个新变量,初始类型相同:
IDiaSymbol *v14; // [esp+30h] [ebp+8h] FORCED BYREFvtbl = DiaSymbol->vtbl;vtbl->get_symTag(DiaSymbol, (int *)&v14);Symbol->Tag = (int)v14;
当然,你可以将其类型和名称更改为更合适的:
int tag; // [esp+30h] [ebp+8h] FORCED BYREFvtbl = DiaSymbol->vtbl;vtbl->get_symTag(DiaSymbol, &tag);Symbol->Tag = tag;
使用联合体表示多态变量
不幸的是,“强制新变量”不适用于寄存器变量(在IDA 7.7中)。在这种情况下,使用联合体可能有效。例如,考虑ntdll.dll
的LdrRelocateImage
函数中的这个片段:
int v6; // esiint v7; // eaxint v8; // ediint v9; // eax v6 = 0; v20 = 0; v7 = RtlImageNtHeader(a1); v8 = v7;if ( !v7 )return-1073741701; v9 = *(unsigned __int16 *)(v7 + 24);if ( v9 == IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { v18 = *(_DWORD *)(v8 + 52); v16 = 0; }else {if ( v9 != IMAGE_NT_OPTIONAL_HDR64_MAGIC )return-1073741701; v18 = *(_DWORD *)(v8 + 48); v16 = *(_DWORD *)(v8 + 52); }
函数RtlImageNtHeader
返回给定地址处PE镜像的IMAGE_NT_HEADERS
结构的指针。在更改其原型和变量类型后,代码变得更易读:
int v6; // esi PIMAGE_NT_HEADERS v7; // eax PIMAGE_NT_HEADERS v8; // ediint Magic; // eaxint v10; // edxint v11; // eaxunsignedint v12; // ecxint v13; // ecxint v15; // [esp+Ch] [ebp-10h]unsignedint v16; // [esp+10h] [ebp-Ch]int v17; // [esp+10h] [ebp-Ch]unsignedint v18; // [esp+14h] [ebp-8h]char *v19; // [esp+14h] [ebp-8h]int v20; // [esp+18h] [ebp-4h] BYREF v6 = 0; v20 = 0; v7 = RtlImageNtHeader(a1); v8 = v7;if ( !v7 )return-1073741701; Magic = v7->OptionalHeader.Magic;if ( Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { v18 = v8->OptionalHeader.ImageBase; v16 = 0; }else {if ( Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC )return-1073741701; v18 = v8->OptionalHeader.BaseOfData; v16 = v8->OptionalHeader.ImageBase; }
然而,有一个小问题。根据魔术值的检查,代码可以处理32位和64位图像,但当前的PIMAGE_NT_HEADERS
类型是32位(PIMAGE_NT_HEADERS32
),因此else
子句中的代码可能不正确。如果我们将v8
更改为PIMAGE_NT_HEADERS64
,则if
子句变得不正确:
if ( Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { ImageBase = HIDWORD(v8->OptionalHeader.ImageBase); v16 = 0; }else {if ( Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC )return-1073741701; ImageBase = v8->OptionalHeader.ImageBase; v16 = HIDWORD(v8->OptionalHeader.ImageBase); }
我们不能强制新变量,因为v8
是在寄存器中分配的,而不是在栈上。我们还能同时使用这两种类型吗?
答案是肯定的:我们可以使用一个联合体来结合这两种类型。以下是如何在此示例中完成的:
-
打开本地类型(Shift-F1); -
添加新类型(Ins); -
输入以下定义:
union nt_headers { PIMAGE_NT_HEADERS32 hdr32; PIMAGE_NT_HEADERS64 hdr64;};
-
将 v8
的类型更改为nt_headers
,并使用“选择联合字段”在if的每个分支中选择正确的字段:
Magic = v7->OptionalHeader.Magic;if ( Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { ImageBase = v8.hdr32->OptionalHeader.ImageBase; ImageBaseHigh = 0; }else {if ( Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC )return-1073741701; ImageBase = v8.hdr64->OptionalHeader.ImageBase; ImageBaseHigh = HIDWORD(v8.hdr64->OptionalHeader.ImageBase); }
在这个特定示例中,差异很小,你可能可以通过一些注释来解决,但在某些情况下,这种方法可能会产生真正的差异。请注意,这种方法也可以用于栈变量。
学习资源
立即关注【二进制磨剑】公众号
原文始发于微信公众号(二进制磨剑):IDA 技巧(79)处理变量重用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论