如何在 RtlAllocateHeap 中获取或存储堆大小以供以后在 RtlFreeHeap 中使用?
How to get or store heap size in RtlAllocateHeap for later use in RtlFreeHeap?
核心问题:一个旧的 Windows XP 游戏在更新的 Windows 版本上冻结,除非它是 运行 具有 Windows XP 兼容模式。
即使没有必要为此提供代码修复,我仍然想修复此冻结,因为 noobie 用户 运行 经常遇到这个问题,每次都询问他们如何才能修理它。应用程序可执行文件当然没有源代码,因此调试冻结并不直接。但是,由于 Windows XP 兼容模式 "fixes" 它,我想知道如何。
所以我在网上查找了一下,发现有一个叫做 Shim Engine 的东西可以通过 AppHelp.dll 提供各种修复。我安装了 Windows Compat Toolkit 并追踪到名为 FaultTolerantHeap 的 Shim 消除了应用程序冻结。到目前为止一切顺利。
关于这个 FTH 的信息不多,但是 this video 来自 Microsoft 的 Calinoiu 先生充分介绍了他们所做的事情以及 FTH 中的 4 个核心策略:
- 总是清零内存
- 为每个分配追加 8 个字节以帮助缓冲 运行s
- 延迟堆释放
- 在正常关机时禁用释放
如视频中所述,这是对来自 ntdll 的 RtlAllocateHeap
和 RtlFreeHeap
等函数完成的。所以我虽然试了一下。
下一步,我将这两个函数与 Microsoft Detours 挂钩并顺利访问。我将调用从我的钩子函数直接重定向到 ntdll.h 中的原始函数。游戏 运行 很好,钩子很好。
然后,我尝试了清零内存:冻结仍然发生。将 8 个字节附加到每个堆分配:冻结仍然发生。禁用内存释放:冻结消失了!啊哈!
所以微软的FTH延迟释放似乎是"cure"冻结。所以下一步是实施延迟免费。我们需要的是一个队列,它接受的字节数最大,在下一次释放之前将清除并释放队列中的第一个条目。 Microsoft 为其队列使用 4MB 缓冲区。所以我可以做我想的一样。但我有一个大问题:我需要跟踪将被释放的堆分配的大小,否则无法判断我的队列有多大。另外我不想排队超大堆,例如位图图像的缓冲区非常大。
对于微软的 FTH,他说他们在每个堆分配的最后附加了他们的魔法字节。像 30 字节 + 8 字节填充。非常多。我想我可以通过添加一些 4 字节值作为一种 "just good enough" 模式来简化它,以从我的 RtlAllocateHeap
挂钩(因为我挂钩 运行 时间)和另外 4 个字节存储每个分配的大小,以便我可以在分配达到 RtlFreeHeap
时访问它。当前代码:
#define FTH_MAGIC_BYTES 0xFEDCBA98ul
#define FTH_ALLOC_PADDING 0
...
static PVOID __stdcall RtlAllocateHeap_hook(PVOID HeapHandle, ULONG Flags, SIZE_T Size)
{
if(Size==0)
{
return RtlAllocateHeap_orig(HeapHandle, Flags, Size);
}
// For our allocations we assume 32bit application:
// treat everything with size of 4 bytes
Size += 8+FTH_ALLOC_PADDING;
PVOID HeapBase = RtlAllocateHeap_orig(HeapHandle, Flags, Size);
if(HeapBase)
{
*(uint32*)((uint32)HeapBase+4) = FTH_MAGIC_BYTES;
*(uint32*)((uint32)HeapBase) = Size;
return (PVOID)((uint32)HeapBase+8);
}
return HeapBase;
};
static BOOL __stdcall RtlFreeHeap_hook(PVOID HeapHandle, ULONG Flags, PVOID HeapBase)
{
if(HeapBase==0)
{
return RtlFreeHeap_orig(HeapHandle, Flags, HeapBase);
}
if(*(uint32*)((uint32)HeapBase-4)==FTH_MAGIC_BYTES)
{
HeapBase = (PVOID)((uint32)HeapBase-8);
}
return RtlFreeHeap_orig(HeapHandle, Flags, HeapBase);
};
它在启动时使应用程序崩溃。有趣的是,当我 运行 调试时它并没有崩溃。在发布时它确实如此。我逐步完成了 Visual Studio 中的程序集,没有发现任何问题。我还监视了新分配的字节以查看字节是否写入堆中 - 它们位于我期望的位置。然而它抛出了应用程序,我不明白为什么。当我不在 RtlAllocateHeap_hook
的 return 上更改 HeapBase
的偏移量时,代码将 运行 就好了。
if(HeapBase)
{
//*(uint32*)((uint32)HeapBase+4) = FTH_MAGIC_BYTES;
//*(uint32*)((uint32)HeapBase) = Size;
return HeapBase;
}
一旦我 return 有了偏移量,应用程序就会再次崩溃。我可以在末尾存储我的字节,然后 return HeapBase 没有偏移量,但是当我们点击 RtlFreeHeap_hook
时找到那个对齐将是疯狂的。我想知道微软是怎么做到的,除非他们当然知道堆的大小。我看到的唯一解决方案是正确使用上述方法,了解如何以 Microsoft 方式检索堆的大小,或者尝试挂钩 malloc 或 new。
有什么想法吗?也许我遗漏了一些非常简单的东西:-)
谢谢。
调用堆栈示例:
ntdll.dll!76f030bd()
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll]
msvcrt.dll!7498f480()
msvcrt.dll!7498f4e6()
msvcrt.dll!7498f52c()
comctl32.dll!73656c62()
game.dat!00413b90()
game.dat!00413fc8()
msvcrt.dll!7499edc8()
msvcrt.dll!7498a53a()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a6a9()
msvcrt.dll!7498d255()
msvcrt.dll!7498d272()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a53a()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498dfd5()
game.dat!0041d4ed()
game.dat!006f0048()
game.dat!004af57d()
game.dat!00413b90()
game.dat!00413fc8()
game.dat!00413b90()
game.dat!00413fc8()
game.dat!004140d8()
game.dat!008fa9ad()
game.dat!0041f018()
EAX = 0262CE78 EBX = 00000000 ECX = 7C7EC8D9 EDX = 00000088 ESI = 00B70000 EDI = 00000000 EIP = 76F030BD ESP = 001886F8 EBP = 00188704 EFL = 00210202
7C7EC8E9 = ????
经过更多测试和阅读后,我在 MSDN 中看到 kernel32.dll 也导出堆函数。 Kernel32 有一个函数 HeapSize
即 returns 我们所需要的。似乎内核函数只是通过 ntdll 对应的通道。
因此我怀疑 ntdll 应该与 HeapSize
对应。我查看了 ntdll 的导出,发现 RtlSizeHeap
。此函数未在 MSDN 上记录。
对 google 的搜索返回了 this page from ntinternals.net。因此,您可以通过使用 GetProcAddress
.
获取其指针来使用内核函数 HeapSize
或 ntdll 函数 RtlSizeHeap
核心问题:一个旧的 Windows XP 游戏在更新的 Windows 版本上冻结,除非它是 运行 具有 Windows XP 兼容模式。
即使没有必要为此提供代码修复,我仍然想修复此冻结,因为 noobie 用户 运行 经常遇到这个问题,每次都询问他们如何才能修理它。应用程序可执行文件当然没有源代码,因此调试冻结并不直接。但是,由于 Windows XP 兼容模式 "fixes" 它,我想知道如何。
所以我在网上查找了一下,发现有一个叫做 Shim Engine 的东西可以通过 AppHelp.dll 提供各种修复。我安装了 Windows Compat Toolkit 并追踪到名为 FaultTolerantHeap 的 Shim 消除了应用程序冻结。到目前为止一切顺利。
关于这个 FTH 的信息不多,但是 this video 来自 Microsoft 的 Calinoiu 先生充分介绍了他们所做的事情以及 FTH 中的 4 个核心策略:
- 总是清零内存
- 为每个分配追加 8 个字节以帮助缓冲 运行s
- 延迟堆释放
- 在正常关机时禁用释放
如视频中所述,这是对来自 ntdll 的 RtlAllocateHeap
和 RtlFreeHeap
等函数完成的。所以我虽然试了一下。
下一步,我将这两个函数与 Microsoft Detours 挂钩并顺利访问。我将调用从我的钩子函数直接重定向到 ntdll.h 中的原始函数。游戏 运行 很好,钩子很好。
然后,我尝试了清零内存:冻结仍然发生。将 8 个字节附加到每个堆分配:冻结仍然发生。禁用内存释放:冻结消失了!啊哈!
所以微软的FTH延迟释放似乎是"cure"冻结。所以下一步是实施延迟免费。我们需要的是一个队列,它接受的字节数最大,在下一次释放之前将清除并释放队列中的第一个条目。 Microsoft 为其队列使用 4MB 缓冲区。所以我可以做我想的一样。但我有一个大问题:我需要跟踪将被释放的堆分配的大小,否则无法判断我的队列有多大。另外我不想排队超大堆,例如位图图像的缓冲区非常大。
对于微软的 FTH,他说他们在每个堆分配的最后附加了他们的魔法字节。像 30 字节 + 8 字节填充。非常多。我想我可以通过添加一些 4 字节值作为一种 "just good enough" 模式来简化它,以从我的 RtlAllocateHeap
挂钩(因为我挂钩 运行 时间)和另外 4 个字节存储每个分配的大小,以便我可以在分配达到 RtlFreeHeap
时访问它。当前代码:
#define FTH_MAGIC_BYTES 0xFEDCBA98ul
#define FTH_ALLOC_PADDING 0
...
static PVOID __stdcall RtlAllocateHeap_hook(PVOID HeapHandle, ULONG Flags, SIZE_T Size)
{
if(Size==0)
{
return RtlAllocateHeap_orig(HeapHandle, Flags, Size);
}
// For our allocations we assume 32bit application:
// treat everything with size of 4 bytes
Size += 8+FTH_ALLOC_PADDING;
PVOID HeapBase = RtlAllocateHeap_orig(HeapHandle, Flags, Size);
if(HeapBase)
{
*(uint32*)((uint32)HeapBase+4) = FTH_MAGIC_BYTES;
*(uint32*)((uint32)HeapBase) = Size;
return (PVOID)((uint32)HeapBase+8);
}
return HeapBase;
};
static BOOL __stdcall RtlFreeHeap_hook(PVOID HeapHandle, ULONG Flags, PVOID HeapBase)
{
if(HeapBase==0)
{
return RtlFreeHeap_orig(HeapHandle, Flags, HeapBase);
}
if(*(uint32*)((uint32)HeapBase-4)==FTH_MAGIC_BYTES)
{
HeapBase = (PVOID)((uint32)HeapBase-8);
}
return RtlFreeHeap_orig(HeapHandle, Flags, HeapBase);
};
它在启动时使应用程序崩溃。有趣的是,当我 运行 调试时它并没有崩溃。在发布时它确实如此。我逐步完成了 Visual Studio 中的程序集,没有发现任何问题。我还监视了新分配的字节以查看字节是否写入堆中 - 它们位于我期望的位置。然而它抛出了应用程序,我不明白为什么。当我不在 RtlAllocateHeap_hook
的 return 上更改 HeapBase
的偏移量时,代码将 运行 就好了。
if(HeapBase)
{
//*(uint32*)((uint32)HeapBase+4) = FTH_MAGIC_BYTES;
//*(uint32*)((uint32)HeapBase) = Size;
return HeapBase;
}
一旦我 return 有了偏移量,应用程序就会再次崩溃。我可以在末尾存储我的字节,然后 return HeapBase 没有偏移量,但是当我们点击 RtlFreeHeap_hook
时找到那个对齐将是疯狂的。我想知道微软是怎么做到的,除非他们当然知道堆的大小。我看到的唯一解决方案是正确使用上述方法,了解如何以 Microsoft 方式检索堆的大小,或者尝试挂钩 malloc 或 new。
有什么想法吗?也许我遗漏了一些非常简单的东西:-)
谢谢。
调用堆栈示例:
ntdll.dll!76f030bd()
[Frames below may be incorrect and/or missing, no symbols loaded for ntdll.dll] msvcrt.dll!7498f480()
msvcrt.dll!7498f4e6()
msvcrt.dll!7498f52c()
comctl32.dll!73656c62()
game.dat!00413b90()
game.dat!00413fc8()
msvcrt.dll!7499edc8()
msvcrt.dll!7498a53a()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a6a9()
msvcrt.dll!7498d255()
msvcrt.dll!7498d272()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a53a()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498a5d6()
msvcrt.dll!7498dfd5()
game.dat!0041d4ed()
game.dat!006f0048()
game.dat!004af57d()
game.dat!00413b90()
game.dat!00413fc8()
game.dat!00413b90()
game.dat!00413fc8()
game.dat!004140d8()
game.dat!008fa9ad()
game.dat!0041f018()EAX = 0262CE78 EBX = 00000000 ECX = 7C7EC8D9 EDX = 00000088 ESI = 00B70000 EDI = 00000000 EIP = 76F030BD ESP = 001886F8 EBP = 00188704 EFL = 00210202
7C7EC8E9 = ????
经过更多测试和阅读后,我在 MSDN 中看到 kernel32.dll 也导出堆函数。 Kernel32 有一个函数 HeapSize
即 returns 我们所需要的。似乎内核函数只是通过 ntdll 对应的通道。
因此我怀疑 ntdll 应该与 HeapSize
对应。我查看了 ntdll 的导出,发现 RtlSizeHeap
。此函数未在 MSDN 上记录。
对 google 的搜索返回了 this page from ntinternals.net。因此,您可以通过使用 GetProcAddress
.
HeapSize
或 ntdll 函数 RtlSizeHeap