[windows内核]分析MmIsAddressValid

如果有过驱动编程经验的人一定会了解这个函数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;
}

MiGetPdeAddressMiGetPteAddress 其实是两个个宏,这个宏我们也可以拿来用。

//++
// 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的代码就很清晰了,首先检测了虚拟地址对应的pdep标志位是否置1,如过置1且pde.ps位置1,就说明这是个2m的大页,检查就完成了。如果不为1,那么将会继续检查PTEp标志位是否置位为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;
 }
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
郁金香VC++过驱动保护全套 免key版 天異赤提供 教程下载地址获取方法: 第一步:打开下方链接,填写QQ邮箱,系统会往QQ邮箱发一封确认订阅邮件 第二步:打开QQ邮箱查看邮件,确认订阅,订阅成功后系统会自动把下载地址和解压密码一起发送到你QQ邮箱http://list.qq.com/cgi-bin/qf_invite?id=585e150c59f30e1213af9a9352367711b2e45c217582cf35 最近时间有些多,一时对网络游戏的保护机制感兴趣了,来研究了一下,听说QQ系列的TesSafe.sys 有些强,于是拿来看看驱动都做了些什 么.以下是对DNF和QQffo(自由幻想)研究结果(xp sp2) 在网上找了些TesSafe的资料,说TesSafe并不怎么样 现在这个版本保护的结果为:任务管理器中可以看到游戏进程,但OD和CE看不见,更不用说什么调试了,iceword可以 看到EPROCSS,但WSysCheck看 郁金香驱动 不见,自己写程序,也不能注入受保护的游戏进程. 可见,NtOpenProcess被Hook了,恢复SSDT后,没有任何效果,可见是inline hook , 用一般的软件检测一下,没有发现inline hook,看来hook得比较深,在网上一找资料才发现,原来的确够隐藏的 郁金香驱动 以下是上一个TesSafe版本的分析结果 从网上找出来的资料,TesSafe.sys保护原理(DNF的保护,我听说,QQ系列游戏的保护机制都是一样的) ================================================================= 保护得比较没有意思,强度也不高.可能隐藏性稍好一些. 用IDA反汇编TesSafe.sys可以看到: 这个驱动一加载,ExAllocPoolWithTag分配了一块内存,然后将一个函数写进这块内存,接着做好保护,然后 PsCreateSystemThread()创建的 郁金香驱动 线程调用ZwUnloadDriver将驱动卸载。虽 然驱动被卸载了,但是ExAllocPoolWithTag分配的内存仍然在起作用。 具体来看它如何进行保护: 郁金香驱动 先是得到了ObOpenObjectByPointer的地址,然后在 NtOpenProcess中搜索0xe8 ,也就是跳转指令,直到遇见RET为止。 一但得到0xe8,比较后面的四个字节,如果转换后为 ObOpenObjectByPointer的地址,就把这个地址用自己代理函数的地址转换后替换掉,达到 保护自己的目的。 郁金香驱动 用WinDbg看了看,果然在我的机器上:0x80570e41这个在 NtOpenProcess中的区域被TesSafe.sys修改,原来这里是:80570e41 e87c8dffff call nt!ObOpenObjectByPointer (80569bc2) 系统通过ObOpenObjectByPointer来通过 EPROCESS得到Handle返回给调用者。TENCENT在这里下了一个跳转:(TesSafe.sys加载后。)80570e41 e826542a78 call f881626c 很明显,系统执行到这里就会调用0x6881626c的函数,也就是 TesSafe.sys的 ObOpenObjectByPointer代理函数。这 样,Client.exe就会在这里被过滤掉,从而让R3程序无法得到QQT的句柄,从而保护进程。 郁金香驱动 ================================================================================= 我手头拿到的版本是2008年8月5号的,把TesSafe逆出来一看,比上个版本有所加强. 在这个新版本中,TesSafe一共InLine Hook了六个函数,我只逆出并恢复了五个 其中: 1. KeAttachProcess NtOpenProcess NtOpenThread 这三个函数的HOOK方式与上一个版本一样,就是上面蓝色字体的方法,把本应该call ObOpenObjectByPointe的代码修改成了call他自己的代码, 然后在他自己的代码中处理保护的进程 上面三个函数,原来正常的代码为 80581ce3 e8a658ffff call nt!ObOpenObjectByPointer (8057758e) 被HOOK后的代码变成了 call a8724af4(TesSafe自己搞出来的函数) lkd> u a8724af

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值