avatar

Catalog
逆向分析MmIsAddressValid(10-10-12)

在对页表基址,页目录表基址熟练掌握后,今天来看逆向分析一个函数:MmIsAddressValid。这是一个系统函数,可以在ntoskrnl.exe的导出函数中找到它,也可以在Windbg中输入指令

Code
1
kd> u MmIsAddressValid

查看。

为什么要分析这个函数呢?因为即使是系统函数,也是无法直接使用物理页的,想要去访问PDE和PTE也就一定要通过基址来访问,而今天要分析的MmIsAddressValid函数,就利用了这么一个原理,相比前一篇的基址小实验,这里对于线性地址拆分的过程更为巧妙,让我们一起来看看吧!

获取PDE属性

首先观察函数主体部分,发现代码并不长,但是有很多跳转,具体跳转内容就不作分析了,主要是分析函数主体:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
804e4661 8bff            mov     edi,edi		//hotpatch
804e4663 55 push ebp
804e4664 8bec mov ebp,esp
804e4666 8b4d08 mov ecx,dword ptr [ebp+8] //取第一个参数(线性地址)
804e4669 8bc1 mov eax,ecx //赋值到中间变量,方便运算
804e466b c1e814 shr eax,14h //逻辑右移20位
804e466e bafc0f0000 mov edx,0FFCh
804e4673 23c2 and eax,edx //和操作数进行与运算,同时清空最后2位;相当于做了一个乘4的运算,既左移2位
804e4675 2d0000d03f sub eax,3FD00000h //进行减法运算,相当于eax+0xC0300000
804e467a 8b00 mov eax,dword ptr [eax] //取PDE的值
804e467c a801 test al,1 //判断PDE属性P位是否为1
804e467e 0f84d2f10000 je nt!MmIsAddressValid+0x4f (804f3856)
804e4684 84c0 test al,al //判断下标为7的位(PS位)值是否为1
804e4686 7824 js nt!MmIsAddressValid+0x53 (804e46ac)

巧妙的与运算:

  1. 首先将线性地址逻辑右移20位,此时还余下12位
  2. 将这12位和操作数0xFFC做一个与运算,0xFFC换算成2进制就是1111 1111 1100。因此做完与运算后,刚刚经过第一步操作还剩下12位的数的低2位,置0了。熟悉移位运算的朋友们知道,这个12位的数,相当于1个10位的数逻辑左移2位得到,换句话说就是将这个10位的数乘4。而这个10位,就是PDI,因此这步操作完了以后,相当于我们获得了PDI*4的值。
  3. 接下来,与0x3FD00000做减法运算,作用相当于加上0xC0300000,两种方法的结果是一样的。因此,我们得到了0xC0300000 + PDI*4的值,而这个值,恰恰就是我们要找的PDE,接着只需要取出里面的值,就可以获取PDE的属性了

后续跳转

再获取PDE的属性后,会遇到两个跳转,简单的概括下:

  1. 首先会判断PDE下标为0的位置的值,也就是P位,当P位为0时,说明物理页无效,会跳转到一个相应的处理函数,这里就不再跟进分析
  2. 若物理页P位为1,就会来到第二个跳转,这里test al, al指令会修改标志寄存器,当al的最高位,也就是下标为7的位置值为1时,会被认为是负数,此时会修改EFLAG寄存器的SF位。这时,在第二个跳转的位置,js判断的就是SF的值是否为1,若为1,也就是al下标为7的位置值为1,这是对应的PDE属性PS位,说明这是一个4MB的大页,进而会跳转执行相应的处理函数。

获取PTE属性

Code
1
2
3
4
5
6
7
8
9
10
11
12
804e4688 c1e90a          shr     ecx,0Ah		//逻辑右移10位
804e468b 81e1fcff3f00 and ecx,3FFFFCh //和操作数进行与运算,同时清空最后2位,相当于PDI左移12位+PTI左移2位
804e4691 81e900000040 sub ecx,40000000h //进行减法运算,相当于eax+0xC0000000
804e4697 8bc1 mov eax,ecx
804e4699 8b08 mov ecx,dword ptr [eax] //获取PTE属性
804e469b f6c101 test cl,1 //判断P位的值是否为0
804e469e 0f84b2f10000 je nt!MmIsAddressValid+0x4f (804f3856)
804e46a4 84c9 test cl,cl //判断PAT是值是否为1
804e46a6 0f88b6de0300 js nt!MmIsAddressValid+0x3f (80522562)
804e46ac b001 mov al,1
804e46ae 5d pop ebp
804e46af c20400 ret 4

巧妙的与运算

Code
1
and ecx, 3FFFFCh

假设线性地址右移10位后的值为 0000 0000 00aa aaaa aaaa bbbb bbbb bbxx(a, b的值为0或者1,这里只是为了区分PDI和PTI)

然后我们来拆分0x3FFFFC的值:0000 0000 0011 1111 1111 1111 1111 1100

将两者进行与运算后得到结果为 0000 0000 00aa aaaa aaaa bbbb bbbb bb00

我们知道,aa aaaa aaaa应为PDI,而bb bbbb bbbb应为PTI,因此可以把得到的结果看作是这样的一个运算:
$$
aa aaaa aaaa<<12 + bb bbbb bbbb<<2
= aa aaaa aaaa * 2^12 + bb bbbb bbbb * 2^2
= aa aaaa aaaa * 1000h + bb bbbb bbbb * 4h
= PDI1000h + PTI4h
$$
因此经过这个与运算后,再通过溢出进行减法运算,获取到PTE的所在位置,便可以取出属性

后续跳转

经过最为关键的步骤,取到了PTE的属性后,还剩下两个跳转,这两个跳转的原理和上述的两个跳转完全一样,分别是取PTE下标为0的P位和下标为7的PAT位,区别仅仅在于,PDE和PTE的下标为7的位的含义不同,所以会跳转进入两个不同的处理函数,这里就不多做分析。

总结

今天的分析主要是弄清楚Windows系统函数是如何获取物理页属性的,同样是通过利用页目录表基址和页表基址,实现用线性地址访问PDE和PTE,在分析时看到,比较关键的一步是两个与运算,非常巧妙,之后再通过减法运算利用溢出达到和加法运算同样的效果,获取到PDE/PTE的值。

参考资料:Joney的笔记,张嘉杰的笔记(群内的两位大佬,由于他们没有开博,先不贴链接了)

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/03/21/%E9%80%86%E5%90%91%E5%88%86%E6%9E%90MmIsAddressValid/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶