如果有过驱动编程经验的人一定会了解这个函数MmIsAddressValid()
,这个函数的作用简单来说就是验证一个地址可否使用,因为在驱动编程里稍有不慎就会导致BSOD,会在读取和写入数据的时候都要非常谨慎,做好充足的判断。
这是MSDN中的解释
https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-mmisaddressvalid
If no page fault would occur from reading or writing at the given virtual address, MmIsAddressValid returns TRUE.
我们再看一下WRK中的解释
BOOLEAN
MmIsAddressValid (
__in PVOID VirtualAddress
)
/*++
Routine Description:
For a given virtual address this function returns TRUE if no page fault
will occur for a read operation on the address, FALSE otherwise.
Note that after this routine was called, if appropriate locks are not
held, a non-faulting address could fault.
Arguments:
VirtualAddress - Supplies the virtual address to check.
Return Value:
TRUE if no page fault would be generated reading the virtual address,
FALSE otherwise.
Environment:
Kernel mode.
--*/
{
return MiIsAddressValid (VirtualAddress, FALSE);
}
WDK文档中给出的功能描述是这样的:
The MmIsAddressValid routine checks whether a page fault will occur
for a read or write operation at a given virtual address.
根据描述来看这个函数的功能只是去检查读写操作会不会触发一个页错误,但是作为一个常用函数,我们常常用这个函数来检查地址合不合法
这次就在MiIsAddressValid (VirtualAddress, FALSE);
里看下具体的流程,主要目的是搞清楚这个函数是怎么判断一个函数会不会触发页错误的。
BOOLEAN
MiIsAddressValid (
IN PVOID VirtualAddress,
IN LOGICAL UseForceIfPossible
)
/*++
Routine Description:
For a given virtual address this function returns TRUE if no page fault
will occur for a read operation on the address, FALSE otherwise.
Note that after this routine was called, if appropriate locks are not
held, a non-faulting address could fault.
Arguments:
VirtualAddress - Supplies the virtual address to check.
UseForceIfPossible - Supplies TRUE if the address should be forced valid
if possible.
Return Value:
TRUE if no page fault would be generated reading the virtual address,
FALSE otherwise.
Environment:
Kernel mode.
--*/
{
PMMPTE PointerPte;
UNREFERENCED_PARAMETER (UseForceIfPossible);
#if defined (_AMD64_)
//
// If this is within the physical addressing range, just return TRUE.
//
if (MI_IS_PHYSICAL_ADDRESS(VirtualAddress)) {
PFN_NUMBER PageFrameIndex;
//
// Only bound with MmHighestPhysicalPage once Mm has initialized.
//
if (MmHighestPhysicalPage != 0) {
PageFrameIndex = MI_CONVERT_PHYSICAL_TO_PFN(VirtualAddress);
if (PageFrameIndex > MmHighestPhysicalPage) {
return FALSE;
}
}
return TRUE;
}
#endif
//
// If the address is not canonical then return FALSE as the caller (which
// may be the kernel debugger) is not expecting to get an unimplemented
// address bit fault.
//
if (MI_RESERVED_BITS_CANONICAL(VirtualAddress) == FALSE) {
return FALSE;
}
#if (_MI_PAGING_LEVELS >= 4)
PointerPte = MiGetPxeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
#endif
#if (_MI_PAGING_LEVELS >= 3)
PointerPte = MiGetPpeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
#endif
PointerPte = MiGetPdeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return TRUE;
}
PointerPte = MiGetPteAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
//
// Make sure we're not treating a page directory as a page table here for
// the case where the page directory is mapping a large page. This is
// because the large page bit is valid in PDE formats, but reserved in
// PTE formats and will cause a trap. A virtual address like c0200000 (on
// x86) triggers this case.
//
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return FALSE;
}
return TRUE;
}
我们目前分析的是32位的,所以看后半部分就行了,如果要看64位的可以参考
X64下MmIsAddressValid的逆向及内存寻址解析
https://bbs.pediy.com/thread-205143.htm
MI_RESERVED_BITS_CANONICAL
在x86上并没有实现
//++
// LOGICAL
// MI_RESERVED_BITS_CANONICAL (
// IN PVOID VirtualAddress
// );
//
// Routine Description:
//
// This routine checks whether all of the reserved bits are correct.
//
// This does nothing on the x86.
//
// Arguments
//
// VirtualAddress - Supplies the virtual address to check.
//
// Return Value:
//
// None.
//
#define MI_RESERVED_BITS_CANONICAL(VirtualAddress) TRUE
_MI_PAGING_LEVELS
代表当前使用的分页机制
#if (_MI_PAGING_LEVELS >= 4)
和#if (_MI_PAGING_LEVELS >= 3)
为真代表了使用了PML4,这是64位系统才用到的
x64使用了4级页表来映射物理内存与虚拟内存。这4级分别是:
PML4(PageMap Level 4)(俗名:PXE),
PDPT(Page Directory Pointers),
PD(PageDirectory)
以及PT(Page Table)。
CR3(控制寄存器)保存着当前进程的PML4基地址(物理地址)。
我们当前分析PAE分页模式使用3级页表,这是我们目前分析x86使用的,分别是
PAPTE
PDE
PTE
所以目前WRK中的MiIsAddressValid 符合我们当前分析系统(x86开启PAE)的分页模式下代码可以稍微简化一下变成
BOOLEAN
MiIsAddressValid (
IN PVOID VirtualAddress,
IN LOGICAL UseForceIfPossible
)
{
PMMPTE PointerPte;
UNREFERENCED_PARAMETER (UseForceIfPossible);
//
// If the address is not canonical then return FALSE as the caller (which
// may be the kernel debugger) is not expecting to get an unimplemented
// address bit fault.
//
if (MI_RESERVED_BITS_CANONICAL(VirtualAddress) == FALSE) {
return FALSE;
}
PointerPte = MiGetPdeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return TRUE;
}
PointerPte = MiGetPteAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
//
// Make sure we're not treating a page directory as a page table here for
// the case where the page directory is mapping a large page. This is
// because the large page bit is valid in PDE formats, but reserved in
// PTE formats and will cause a trap. A virtual address like c0200000 (on
// x86) triggers this case.
//
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return FALSE;
}
return TRUE;
}
MiGetPdeAddress
和MiGetPteAddress
其实是两个个宏,这个宏我们也可以拿来用。
//++
// PMMPTE
// MiGetPdeAddress (
// IN PVOID va
// );
//
// Routine Description:
//
// MiGetPdeAddress returns the address of the PDE which maps the
// given virtual address.
//
// Arguments
//
// Va - Supplies the virtual address to locate the PDE for.
//
// Return Value:
//
// The address of the PDE.
//
//--
#define MiGetPdeAddress(va) \
((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PDI_SHIFT) << PTE_SHIFT) + PDE_BASE))
//++
// PMMPTE
// MiGetPteAddress (
// IN PVOID va
// );
//
// Routine Description:
//
// MiGetPteAddress returns the address of the PTE which maps the
// given virtual address.
//
// Arguments
//
// Va - Supplies the virtual address to locate the PTE for.
//
// Return Value:
//
// The address of the PTE.
//
//--
#define MiGetPteAddress(va) \
((PMMPTE)(((((ULONG_PTR)(va) & VIRTUAL_ADDRESS_MASK) >> PTI_SHIFT) << PTE_SHIFT) + PTE_BASE))
//
// The number of bits in a virtual address.
//
#define VIRTUAL_ADDRESS_BITS 48
#define VIRTUAL_ADDRESS_MASK ((((ULONG_PTR)1) << VIRTUAL_ADDRESS_BITS) - 1)
#define PDI_SHIFT_X86PAE 21
#define PDI_SHIFT 21
#define PTE_SHIFT 3
#define PDE_BASE 0xc0600000
#define PTE_BASE 0xc0000000
这里应该是判断是否是大页2m/4m,这里是PAE下是2M
if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return TRUE;
}
#define MI_PDE_MAPS_LARGE_PAGE(PDE) ((PDE)->u.Hard.LargePage == 1)
可以看到虚拟地址右移18位后与上了0x3FF8,这里可以看成虚拟地址右移21位然后左移3位(乘8,每个分页结构的大小都为8字节,低三位都为0),当时看到这里的有一些不理解,因为现在还剩下11位(也就是2 9 9 12分页的前两个偏移)。这里直接用这11位加上了0xC0600000,这里在Windbg中进行分析
在Windbg中可以看到0xC0600000指向的是PDPT[0]
,,因此上面的用11位直接加上0xC0600000
就好理解了,前2位用来索引是哪个PDPT[n]
,后9位用来确定PDE
中的偏移,这里也对应了PDT的计算公式PDE = ((VA >> 21) << 3) + PDE_BASE
。因為 PDE_BASE 是描述PTE_BASE的PTE所以PDE_BASE = (PTE_BASE >> 12) << 3 + PTE_BASE = (0xC0000000 >> 12) << 3 + 0xC0000000 = 0xC0600000
。
Windows应该会在0xC0600000地址翻译过程中的PTE
构造一个PDPT[n]
的表,这里可以用Windbg验证。
在WIndbg中可以看到在翻译虚拟地址0xC0600000
时,PTE
中存放了一组PDPT
,和CR3
中储存的值一致,因此(address >> 18) & 0x3FF8
这个操作既起到了计算PDE偏移的作用,同时也计算了位于哪个PDPT
中。
因此IDA中的MmIsAddressValid
的代码就很清晰了,首先检测了虚拟地址对应的pde
的p
标志位是否置1,如过置1且pde.ps
位置1,就说明这是个2m的大页,检查就完成了。如果不为1,那么将会继续检查PTE
的p
标志位是否置位为1,如果为1则检查PAT
位。所以MmIsAddressValid
只是简单的检查页面的p标志位。
正如msdn中所说,如果这个地址指向的物理页没有被锁定或者不是非分页池分配的内存,那么这个函数返回的结果就不一定正确。
如果真正需要判断一个地址是否具有读写属性的话还需要使用其他函数来判断,MmIsAddressValid
只是简单的判断了一下P
位而已
这里列举两个判断地址读写属性的函数ProbeForRead()
,ProbeForWrite()
。这两个函数才是来验证地址是否可读写的函数,但是仅限于用户地址空间的地址。感兴趣的也可以去WRK中观看其中的源码,这里就不展开说了。
最后我们也可以看一下ReactOS 0.4.15中MmIsAddressValid
的实现
BOOLEAN NTAPI MmIsAddressValid (IN PVOID VirtualAddress)
{
#if _MI_PAGING_LEVELS >= 4
/* Check if the PXE is valid */
if (MiAddressToPxe(VirtualAddress)->u.Hard.Valid == 0) return FALSE;
#endif
#if _MI_PAGING_LEVELS >= 3
/* Check if the PPE is valid */
if (MiAddressToPpe(VirtualAddress)->u.Hard.Valid == 0) return FALSE;
#endif
#if _MI_PAGING_LEVELS >= 2
/* Check if the PDE is valid */
if (MiAddressToPde(VirtualAddress)->u.Hard.Valid == 0) return FALSE;
#endif
/* Check if the PTE is valid */
if (MiAddressToPte(VirtualAddress)->u.Hard.Valid == 0) return FALSE;
/* This address is valid now, but it will only stay so if the caller holds
* the PFN lock */
return TRUE;
}