|
psmd->smb = *psmb;
for (i = 0; i < psmb->dBytes; i++)
{
psmd->awData [i] =
(SpyMemoryTestAddress (psmb->pbAddress + i)
? SPY_MEMORY_DATA_VALUE (psmb->pbAddress [i], TRUE)
: SPY_MEMORY_DATA_VALUE (0, FALSE));
}
}
else
{
if (dSize >= SPY_MEMORY_DATA_)
{
psmd->smb.pbAddress = NULL;
psmd->smb.dBytes = 0;
}
n = 0;
}
return n;
}
// -----------------------------------------------------------------
BOOL SpyMemoryTestAddress (PVOID pVirtual)
{
return SpyMemoryPageEntry (pVirtual, NULL);
}
// -----------------------------------------------------------------
BOOL SpyMemoryTestBlock (PVOID pVirtual,
DWORD dBytes)
{
PBYTE pbData;
DWORD dData;
BOOL fOk = TRUE;
if (dBytes)
{
pbData = (PBYTE) ((DWORD_PTR) pVirtual & X86_PAGE_MASK);
dData = (((dBytes + X86_OFFSET_4K (pVirtual) - 1)
/ PAGE_SIZE) + 1) * PAGE_SIZE;
do {
fOk = SpyMemoryTestAddress (pbData);
pbData += PAGE_SIZE;
dData -= PAGE_SIZE;
}
while (fOk && dData);
}
return fOk;
}
列表 4-24. 复制内存块中的数据
SpyMemoryTestAddress() 用于测试数据的有效性, SpyMemoryReadBlock() 针对要读取的每个字节都会调用 SpyMemoryTestAddress() 。 SpyMemoryTestAddress() 在 列表 4-24 的下半部分给出,该函数只是简单的调用 SpyMemoryPageEntry() ,不过传入的第二个参数为 NULL 。 SpyMemoryPageEntry() 在讨论 SPY_IO_PAGE_ENTRY 时已经介绍过( 列表 4-22 )。将其 PSPY_PAGE_ENTRY 指针参数设为 NULL ,意味着调用者不关心指定线性地址对应的 page entry ,因此,如果线性地址有效,函数将返回 TRUE 。在 SpyMemoryPageEntry() 的上下文中,仅当一个线性地址对应的数据页存在于物理内存中,或者位于页面文件中,该地址才是有效的。注意,这种行为与 ntoskrnl.exe 中的 API 函数 MmIsAddressValid() 并不一致,当指定的页不存在于物理内存中时, MmIsAddressValid() 总是返回 FALSE ,即使这个有效的数据据页位于页面文件中也会如此。 列表 4-24 中的另一个函数 SpyMemoryTestBlock() 是 SpyMemoryTestAddress() 的增强版。它可测试一个内存区域的有效性,它每次可测试指定块中的 4,096 个字节,直到测试完区域中的所有页为止。
#define SPY_MEMORY_DATA_N(_n) \
struct _SPY_MEMORY_DATA_##_n \
{ \
SPY_MEMORY_BLOCK smb; \
WORD awData [_n]; \
}
typedef SPY_MEMORY_DATA_N (0)
SPY_MEMORY_DATA, *PSPY_MEMORY_DATA, **PPSPY_MEMORY_DATA;
#define SPY_MEMORY_DATA_ sizeof (SPY_MEMORY_DATA)
#define SPY_MEMORY_DATA__(_n) (SPY_MEMORY_DATA_ + ((_n) * WORD_))
#define SPY_MEMORY_DATA_BYTE 0x00FF
#define SPY_MEMORY_DATA_VALID 0x0100
#define SPY_MEMORY_DATA_VALUE(_b,_v) \
((WORD) (((_b) & SPY_MEMORY_DATA_BYTE ) | \
((_v) ? SPY_MEMORY_DATA_VALID : 0)))
列表 4-25. SPY_MEMORY_DATA 的定义
将置换出去的页作为有效的地址范围有一个很重要的好处:当 SpyMemoryReadBlock() 试图读取这些页中的第一个字节时,这些页就会被很快的再次调入内存中。稍后给出的内存 Dump 工具如果依赖 MmIsAddressValid() ,有时就会拒绝显示指定地址范围中的数据(即使 5 分钟之前,它还可以显示这些数据),而这仅仅是因为这些页可能已被传送到了页面文件中。
IOCTL 函数 SPY_IO_MEMORY_BLOCK
SPY_IO_MEMORY_BLOCK 依赖于 SPY_IO_MEMORY_DATA ,因为它也是从任意地址复制内存块到调用者的缓冲区中。主要的区别是: SPY_IO_MEMORY_DATA 试图复制所有可读取的字节,而对于 SPY_IO_MEMORY_BLOCK 来说,只要请求的范围中包含无效地址它就会失败,一个字节也不会复制。第 6 章中需要这个函数来将位于内核空间中的数据结构传递给用户模式下的程序。这一要求显然会大大限制这个函数,所以若一个结构体中包含无法读取的字节,就跳过它们,仅复制可读取的字节。
和 SPY_IO_MEMORY_DATA 类似, SPY_IO_MEMORY_BLOCK 期望输入一个 SPY_MEMORY_BLOCK 结构来指定要复制的内存块的基地址和大小。返回的数据,将是原始数据的 1:1 复制品。输出缓冲区必须足够容纳要复制的全部内容。否则,将会报告一个错误,并且不会返回任何数据。
IOCTL 函数 SPY_IO_HANDLE_INFO
和前面介绍的 SPY_IO_PHSICAL 类似,这个函数允许用户模式下的程序调用其他途经无法调用的内核模式 API 。内核驱动程序可通过 ntoskrnl.exe 导出的 obReferenceObjectByHandle() 来获取由句柄描述的对象的指针。而在 Win32 下没有对等的函数。不过,应用程序可以命令 Spy 设备执行这一函数,并返回对象的指针。 列表 4-26 展示了由 SpyDispatcher() 调用的 SpyOutputHandleInfo() 函数。可通过 SpyInputHandle() 获(定义于 列表 4-10 )取输入的句柄。
列表 4-26 顶部的 SPY_HANDLE_INFO 结构包含与句柄相关的对象体的指针,以及该句柄的属性,这两个都会由 ObReferenceObjectByHandle() 返回。特别重要的一点是:如果 ObReferenceObjectByHandle() 调用成功,就必须调用 ObDereferenceObject() 来将对象的引用计数器恢复到先前的值。如果没有这样做,将会导致“对象引用漏洞”。
typedef struct _SPY_HANDLE_INFO
{
PVOID pObjectBody;
DWORD dHandleAttributes;
}
SPY_HANDLE_INFO, *PSPY_HANDLE_INFO, **PPSPY_HANDLE_INFO;
#define SPY_HANDLE_INFO_ sizeof (SPY_HANDLE_INFO)
// -----------------------------------------------------------------
NTSTATUS SpyOutputHandleInfo (HANDLE hObject,
PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_HANDLE_INFO shi;
OBJECT_HANDLE_INFORMATION ohi;
NTSTATUS ns = STATUS_INVALID_PARAMETER;
if (hObject != NULL)
{
ns = ObReferenceObjectByHandle (hObject,
STANDARD_RIGHTS_READ,
NULL, KernelMode,
&shi.pObjectBody, &ohi);
}
if (ns == STATUS_SUCCESS)
{
shi.dHandleAttributes = ohi.HandleAttributes;
ns = SpyOutputBinary (&shi, SPY_HANDLE_INFO_,
pOutput, dOutput, pdInfo);
ObDereferenceObject (shi.pObjectBody);
}
return ns;
} 上一页 [1] [2] |