像往常一样,我的反调试相关帖子,一切都从微软没有记录的一点无害标志开始。或者至少我是这么认为的。
这次的主要攻击者是NtMapViewOfSection
,一个可以将段对象映射到给定进程的地址空间的系统调用,主要用于实现共享内存和内存映射文件(Win32 API 将是MapViewOfFile)。
NTSTATUS NtMapViewOfSection(
HANDLE SectionHandle,
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
SECTION_INHERIT InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect);
通过在ntoskrnl
's 中进行一些挖掘MiMapViewOfSection
并在 Windows 标头中搜索已知常量,我们可以恢复大多数有效标志值背后的含义。
/* Valid values for AllocationType */
MEM_RESERVE 0x00002000
SEC_PARTITION_OWNER_HANDLE 0x00040000
MEM_TOPDOWN 0x00100000
SEC_NO_CHANGE 0x00400000
SEC_FILE 0x00800000
MEM_LARGE_PAGES 0x20000000
SEC_WRITECOMBINE 0x40000000
最初我失败了ctrl+f
并且没有意识到这0x2000
是一个已知的标志,所以我开始深入挖掘。在同一个函数中,我们还可以发现标志的作用及其主要限制。
// --- MAIN FUNCTIONALITY ---
if (SectionOffset + ViewSize > SectionObject->SizeOfSection &&
!(AllocationAttributes & 0x2000))
return STATUS_INVALID_VIEW_SIZE;
// --- LIMITATIONS ---
// Image sections are not allowed
if ((AllocationAttributes & 0x2000) &&
SectionObject->u.Flags.Image)
return STATUS_INVALID_PARAMETER;
// Section must have been created with one of these 2 protection values
if ((AllocationAttributes & 0x2000) &&
!(SectionObject->InitialPageProtection & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)))
return STATUS_SECTION_PROTECTION;
// Physical memory sections are not allowed
if ((Params->AllocationAttributes & 0x20002000) &&
SectionObject->u.Flags.PhysicalMemory)
return STATUS_INVALID_PARAMETER;
现在,这听起来像是一个沼泽标准MEM_RESERVE
,您也可以VirtualAlloc(MEM_RESERVE)
随心所欲,但是与此内存交互的 API 会以不同的方式对待它。
你可能会问有多大不同?好吧,在错误地将标志识别为未记录后,我继续尝试创建我可能创建的最大部分。一切都很顺利,直到我打开ProcessHacker内存视图。PC 几乎无法使用至少一分钟,此后黑客也有一段时间没有响应。随后的运行似乎没有抓住了整个系统但是它仍然采取长达4分钟的NtQueryVirtualMemory
调用返回。
我想你可以像鲍勃·罗斯所说的那样把这称为快乐的小事故。
由于我很懒,所以我决定使用Windows Performance Recorder而不是潜入和倒退。这是一个使用 ETW 跟踪的漂亮工具,可以让您深入了解系统上发生的事情。然后可以在Windows 性能分析器中查看记录的跟踪。
这并没有说太多,但至少我们知道在哪里看。
在花了更多时间盯着每个人最喜欢的反编译器中的代码之后,它变得更加清楚发生了什么。我敢打赌,它会遍历给定内存范围的每个页表条目。而且因为我们一次处理数 TB 的数据,所以迭代次数超过 10 亿次。(MiQueryAddressState
是一个很大的函数,我不认为一个简短的伪代码片段可以做到公正)
从我的测试来看,视图大小和所用时间之间的关系是完全线性的,这一事实也加强了这一点。为了进一步验证这个想法,我们还可以做一些快速的餐巾纸数学计算,看看它是否全部加起来:
instructions per second (ips) = 3.8Ghz * ~8
page table entries (n) = 12TB / 4096
time taken (t) = 3.5 minutes
instruction per page table entry = ips * t / n = ~2000
在我看来,这个数字看起来相当可信,所以,把所有的东西加起来,我会坚持当前的想法。
// file handle must be a handle to a non empty file
void* section = nullptr;
auto status = NtCreateSection(§ion,
MAXIMUM_ALLOWED,
nullptr,
nullptr,
PAGE_EXECUTE_READWRITE,
SEC_COMMIT,
file_handle);
if (!NT_SUCCESS(status))
return status;
// Personally largest I could get the section was 12TB, but I'm sure people with more
// memory could get it larger.
void* base = nullptr;
for (size_t i = 46; i > 38; --i) {
SIZE_T view_size = (1ull << i);
status = NtMapViewOfSection(section,
NtCurrentProcess(),
&base,
0,
0x1000,
nullptr,
&view_size,
ViewUnmap,
0x2000, // <- the flag
PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(status))
break;
}
请注意,理想情况下,您需要用这些部分包围代码,因为只有这些部分的保留部分会导致速度变慢。此外,事务也可以是需要非空文件的解决方案,而无需触及任何已存在的内容或创建用户可见的内容。
我认为这是一种伟大而强大的技术,可以让人们分析您的代码。资源使用是合理的,设置它只需要几个系统调用,并且不太可能被意外触发。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。