练习逆向XP内核2-9-9-12和10-10-12分页的MmIsAddressValid函数
前言:这篇笔记记录逆向XP内核MmIsAddressValid函数的学习
这个MmIsAddressValid函数就是判断对一个传入的线性地址是否有效,这里的有效指的就是判断可以将其转换为对应的物理地址,那么其中肯定就有对应的PDE PTE,如果都存在的话,并且P位有效等,那么也就是一个有效的地址
这个函数里面还判断了相关的PDE的PS位,是否为大页,还有PTE的PAT位,这个PAT位后面还走了一个跳转的流程,这个不知道具体是干什么的
然后还有一个重要的知识点,就是通过逆向这个函数,你会发现操作系统在找线性地址对应的物理页的时候,它的整体流程,通过C0300000页目录基址,C0000000页表基址来进行寻找
如何寻找MmIsAddressValid内核函数的方法
知识点:
1、XP系统下的2-9-9-12分页模式通过内核文件ntkrnlpa.exe来进行观察
2、XP系统下的10-10-12分页模式通过内核文件ntoskrnl.exe来进行观察
都是逆向可以用两个工具一个是ida,一个是windbg,windbg的话直接u MmIsAddressValid L40
即可,如下图所示
因为当前XP是10-10-12分页下的,所以拿内核文件ntoskrnl.exe来进行分析,这里通过IDA来进行分析,ALT+T来进行全局搜索字符串MmIsAddressValid,
如下图所示,我这里用的是全局搜索字符串,或者直接在左边函数列表中Ctrl+F来搜索也可以
.text:0040AEC6 ; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
.text:0040AEC6 public MmIsAddressValid
.text:0040AEC6 MmIsAddressValid proc near ; CODE XREF: sub_40AF1C+C↓p
.text:0040AEC6 ; sub_4129D8+2F5B↓p ...
.text:0040AEC6
.text:0040AEC6 VirtualAddress = dword ptr 8
.text:0040AEC6
.text:0040AEC6 ; FUNCTION CHUNK AT .text:0041DDA5 SIZE 00000007 BYTES
.text:0040AEC6 ; FUNCTION CHUNK AT .text:0044F150 SIZE 00000019 BYTES
.text:0040AEC6
.text:0040AEC6 mov edi, edi
.text:0040AEC8 push ebp
.text:0040AEC9 mov ebp, esp
.text:0040AECB mov ecx, [ebp+VirtualAddress]
.text:0040AECE mov eax, ecx
.text:0040AED0 shr eax, 14h
.text:0040AED3 mov edx, 0FFCh
.text:0040AED8 and eax, edx
.text:0040AEDA sub eax, 3FD00000h
.text:0040AEDF mov eax, [eax]
.text:0040AEE1 test al, 1
.text:0040AEE3 jz loc_41DDA5
.text:0040AEE9 test al, al
.text:0040AEEB js short loc_40AF11
.text:0040AEED shr ecx, 0Ah
.text:0040AEF0 and ecx, 3FFFFCh
.text:0040AEF6 sub ecx, 40000000h
.text:0040AEFC mov eax, ecx
.text:0040AEFE mov ecx, [eax]
.text:0040AF00 test cl, 1
.text:0040AF03 jz loc_41DDA5
.text:0040AF09 test cl, cl
.text:0040AF0B js loc_44F150
.text:0040AF11
.text:0040AF11 loc_40AF11: ; CODE XREF: MmIsAddressValid+25↑j
.text:0040AF11 ; MmIsAddressValid+44298↓j
.text:0040AF11 mov al, 1
.text:0040AF13
.text:0040AF13 loc_40AF13: ; CODE XREF: MmIsAddressValid+12EE1↓j
.text:0040AF13 pop ebp
.text:0040AF14 retn 4
.text:0040AF14 MmIsAddressValid endp
...
...
.text:0044F150 ; START OF FUNCTION CHUNK FOR MmIsAddressValid
.text:0044F150
.text:0044F150 loc_44F150: ; CODE XREF: MmIsAddressValid+45↑j
.text:0044F150 and eax, edx
.text:0044F152 mov eax, [eax-3FD00000h]
.text:0044F158 and ax, 81h
.text:0044F15C cmp al, 81h
.text:0044F15E jnz loc_40AF11
.text:0044F164 jmp loc_41DDA5
.text:0044F164 ; END OF FUNCTION CHUNK FOR MmIsAddressValid
10-10-12分页逆向过程
位运算的知识点:
.text:0040AEED shr ecx, 0Ah
.text:0040AEF0 and ecx, 3FFFFCh
对于这两条,我一开始看不懂,为什么这样的位运算可以使得PDI*4096+PTI*4
,然后我自己想了下,应该是获取10-10-12的 10 和10的时候,此时 >> 10
,相当于少右移了2位,那么整体多左移了2位,那么对于PDI的话,其实就是右移了12位也就是2^(10+2)
那么是*4096
,而对于PTI的话就是右移了2位也就是2^(0+2)
次,那么*4
思考
从这个函数的入口可以看到mov edi,edi
的指令,好像没什么用,因为下面都没看到有使用该寄存器,实际上是用到的:
参考文章:https://blog.csdn.net/swanabin/article/details/17550897
很明显,两个字节的MOV EDI,EDI
指令什么事情也不做?
那么,就有两个问题:
第一,为什么不直接从函数体开始而要从这条什么都不做的指令开始呢?
第二,即使需要在函数一开始空出两个字节,为什么不直接使用两条NOP指令,而要使用这条MOV指令呢?
在网上查阅一些资料后,得到了答案:
对于第一个问题,答案是为了实现hot-patching技术,即运行时修改一个函数的行为。修改过程如下:把MOV EDI, EDI修改为一条短跳转指令(一条短跳转指令恰好两个字节),把MOV EDI, EDI上面的五个NOP修改为一条长跳转指令(一条长跳转指令恰好五个字节),短跳转指令跳到长跳转指令上,长跳转指令跳到修改后的函数体上。
这个指令可以用在与0环交互的时候,比如中断门就需要两个字节,刚刚好满足条件,后面驱动篇章实现去写拷贝监控的项目中就会用到
参考文章: https://www.cnblogs.com/zpchcbd/p/15949802.html
对于第二个问题,答案是为了提高效率。执行一条MOV指令比执行两条NOP指令花费更少的时间。
注意点
逆向的时候可以看到,偏移有些写的是sub ecx, 40000000h
和sub eax, 3FD00000h
,实际上转换下为:
sub ecx, 40000000h
-> add ecx, C0000000h
sub eax, 3FD00000h
-> add eax, C0300000h
2-9-9-12分页逆向过程
这个是后面学了2-9-9-12以后补上的
2-9-9-12分页模式通过内核文件ntkrnlpa.exe来进行观察
同样的还是用IDA来进行分析
.text:0043CA48 ; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
.text:0043CA48 public MmIsAddressValid
.text:0043CA48 MmIsAddressValid proc near ; CODE XREF: sub_41ADD4+2F↑p
.text:0043CA48 ; sub_41AE26+29↑p ...
.text:0043CA48
.text:0043CA48 var_8 = dword ptr -8
.text:0043CA48 var_4 = dword ptr -4
.text:0043CA48 VirtualAddress = dword ptr 8
.text:0043CA48
.text:0043CA48 mov edi, edi ; hot-patching技术
.text:0043CA4A push ebp
.text:0043CA4B mov ebp, esp
.text:0043CA4D push ecx
.text:0043CA4E push ecx
.text:0043CA4F mov ecx, [ebp+VirtualAddress] ; ecx = VirtualAddress 也就是压入的线性地址
.text:0043CA52 push esi ; 保存原来的esi,后面需要用到esi作为计算
.text:0043CA53 mov eax, ecx ; eax = VirtualAddress
.text:0043CA55 shr eax, 12h ; VirtualAddress = VirtualAddress >> 18
.text:0043CA58 mov esi, 3FF8h ; esi = 0x3FF8
.text:0043CA5D and eax, esi ; VirtualAddress = VirtualAddress & 0x3FF8 相当于获取的是PDPTEI*8*512+PDI*8
.text:0043CA5F sub eax, 3FA00000h ; eax = PDPTEI*8*512+PDI*8+0xC0600000,相当于拿到了PDE的地址
.text:0043CA64 mov edx, [eax] ; 获取PDE结构地址中存储的值
.text:0043CA66 mov eax, [eax+4] ; 获取当前PDE结构中得下一个PDE结构地址中存储的值
.text:0043CA69 mov [ebp+var_4], eax ; 把第二个获取的PDE结构地址中的值存储到堆栈中
.text:0043CA6C mov eax, edx ; eax = 第一个获取的PDE结构地址中存储的值
.text:0043CA6E push edi
.text:0043CA6F and eax, 1 ; 判断是否第一个获取的PDE结构地址中存储的值,就可以判断当前PDE结构P位是否有效
.text:0043CA72 xor edi, edi
.text:0043CA74 or eax, edi ; 判断是否当前PDE结构中的值是否为空
.text:0043CA76 jz short loc_43CAD9 ; 如果为空则结束
.text:0043CA78 mov edi, 80h
.text:0043CA7D and edx, edi ; 判断PDE是否为大页,也就是判断PS位,第7位
.text:0043CA7F push 0
.text:0043CA81 mov [ebp+var_8], edx ; 判断PS位的结果存储到堆栈中
.text:0043CA84 pop eax ; 清空eax
.text:0043CA85 jz short loc_43CA8B ; 如果不是大页,PS位不为1,那跳转到0x43CA8B进行处理
.text:0043CA87 test eax, eax
.text:0043CA89 jz short loc_43CADD ; 如果走到这里,那么就肯定会跳转到43CADD,意味着这个函数的结束
.text:0043CA8B
.text:0043CA8B loc_43CA8B: ; CODE XREF: MmIsAddressValid+3D↑j
.text:0043CA8B shr ecx, 9 ; 下面是PDE PS位0 物理页为4kb的情况的处理,VirtualAddress = VirtualAddress >> 9
.text:0043CA8E and ecx, 7FFFF8h ; ecx相当于获取的是PDPTEI*512*512*8+PDI*8*512+PTI*8
.text:0043CA94 mov eax, [ecx-3FFFFFFCh]
.text:0043CA9A sub ecx, 40000000h ; ecx = C0000000+PDPTEI*512*512*8+PDI*8*512+PTI*8
.text:0043CAA0 mov edx, [ecx] ; 获得指定的PTE结构地址中的值
.text:0043CAA2 mov [ebp+var_4], eax
.text:0043CAA5 push ebx
.text:0043CAA6 mov eax, edx ; edx = PTE结构地址中的值
.text:0043CAA8 xor ebx, ebx
.text:0043CAAA and eax, 1 ; 判断有效位P
.text:0043CAAD or eax, ebx ; 判断当前PTE结构中的值有没有
.text:0043CAAF pop ebx
.text:0043CAB0 jz short loc_43CAD9 ; 如果没有值的话,那么函数结束
.text:0043CAB2 and edx, edi ; 判断PTE的PAT位,第7位
.text:0043CAB4 push 0
.text:0043CAB6 mov [ebp+var_8], edx
.text:0043CAB9 pop eax
.text:0043CABA jz short loc_43CADD ; 如果PAT位为0则跳转
.text:0043CABC test eax, eax ; 这个不知道再判断什么,但是这里的会正常来说不会跳转,一直往下走
.text:0043CABE jnz short loc_43CADD
.text:0043CAC0 and ecx, esi ; PDE的地址 & 3FF8 看不太懂
.text:0043CAC2 mov ecx, [ecx-3FA00000h] ; (PDE的地址 & 3FF8) + 0xC0600000,这个不知道获得的是什么结构体,老师没讲过
.text:0043CAC8 mov eax, 81h
.text:0043CACD and ecx, eax
.text:0043CACF xor edx, edx
.text:0043CAD1 cmp ecx, eax ; 同样也会判断第7位和第0位
.text:0043CAD3 jnz short loc_43CADD
.text:0043CAD5 test edx, edx
.text:0043CAD7 jnz short loc_43CADD
.text:0043CAD9
.text:0043CAD9 loc_43CAD9: ; CODE XREF: MmIsAddressValid+2E↑j
.text:0043CAD9 ; MmIsAddressValid+68↑j
.text:0043CAD9 xor al, al
.text:0043CADB jmp short loc_43CADF
.text:0043CADD ; ---------------------------------------------------------------------------
.text:0043CADD
.text:0043CADD loc_43CADD: ; CODE XREF: MmIsAddressValid+41↑j
.text:0043CADD ; MmIsAddressValid+72↑j ...
.text:0043CADD mov al, 1
.text:0043CADF
.text:0043CADF loc_43CADF: ; CODE XREF: MmIsAddressValid+93↑j
.text:0043CADF pop edi
.text:0043CAE0 pop esi
.text:0043CAE1 leave
.text:0043CAE2 retn 4
.text:0043CAE2 MmIsAddressValid endp