X64下MmIsAddressValid的逆向及内存寻址解析
作 者: 普通朋友
时 间: 2015-10-21,20:03:52
链 接: http://bbs.pediy.com/showthread.php?t=205143
在内核编程时,经常会用到MmIsAddressValid来检测地址是否有效,结合之前学过的虚拟地址到物理地址之间的转化,所以发一篇对该函数的逆向以及代码还原,x86的资料论坛以及网络有很多了,所以这里楼主只谈一下Win7 x64下的MmIsAddressValid过程分析
要检测地址是否有效以及逆向MmIsAddressValid,还是得先从 x64下线性地址的格式入手
为了方便起见,楼主只说一下最普通的4k分页的情况,其他的可以举一反三
从这张图上,可以看到虽然线性地址有64位,但实际只用了48位,高16位其实是符号拓展位, 根据这高16位也可以做一个简单的内存地址是否有效判断(后面逆向会谈到)
从上面这张图,可以看到线性地址被拆开成了五个部分, 还有四张表分别是:
PML4T : 大小为4k ,每项8 字节 ,每一项指向PDPT的首地址
PDPT : 大小为4k ,每项8 字节 , 每一项指向PD的首地址
PD : 大小为4k ,每项8 字节 , 每一项指向PT的首地址
PT: 大小为4k ,每项8 字节 , 每一项存放一个物理页
如果从编程的角度来说,这四张表其实就是四个数组,数组的每一项可以看做是一个指针
线性地址被拆开成了五个部分,前四个部分都可以看做是数组的下标
举个例子,比如线性地址的39-47位,可以看做是一个Pml4t的下标Index,那么在代码里相当于
Pml4t[Index * 8]这样的寻址
其他的数组的访问依此类推
最后在PT的数组里找到物理页后,再加上线性地址的低12位,便是最终的物理地址
上面这张图是每一张表项的结构图,其中比较重要的是第0位,也就是P位,用于检测所指的页面或页表当前是否加载到了物理存储器中 ,p = 0即为无效
了解了理论,再来手工解析一下
随便写一个程序如下:
字符串地址在0000000140092000
字符串内容是NoteBook
首先,对该虚拟地址做一个格式转化
0 101 000000000 010010010 000000000000
0 5 0 0x92 0
由低到高分成了五个部分,前四个是四个数组的下标,最后的0是偏移
第一步,找到该进程的CR3的值,也就是第一个表PML4T的首地址
地址在4e37b000 处 ,注意,该地址是物理地址 ,并且根据格式,低十二位需要清0
第二步,找到该进程PDPT 的首地址,根据之前的下标,得到在PML4T[0]处:
对齐后,PDPT的首地址在4d1cc000处
第三步,找到该进程PDT的首地址,根据之前的下标,得到在PDPT [5]处:
对齐后,PDT的首地址在4d8cd000处
第四步,找到该进程PT的首地址,根据之前的下标,得到在PDT [0]处:
对齐后,PT的首地址在4de4e000处
第五步,找到对应的物理页,根据之前的下标,得到在PT[0x92]处:
对齐后,数据物理页在4cdfa000处
最后一步,得到完整的物理地址:
和程序中的字符串内容一样
说了这么多,进入逆向MmIsAddressValid
.text:00000001400AD930 _MmIsAddressValid proc near .text:000000014000FB6E
.text:00000001400AD930
.text:00000001400AD930 mov rax, rcx
.text:00000001400AD933 sar rax, 48
;取得线性地址的 高16位
.text:00000001400AD937 inc rax
.text:00000001400AD93A cmp rax, 1
.text:00000001400AD93E ja loc_1400AD9D3
; 高16位要么全0, 要么全1 ,加一后大于1则不合法,直接返回false
.text:00000001400AD944 mov rax, rcx
.text:00000001400AD947 mov rdx, 0FFFFF6FB7DBED000h
; PML4T 的虚拟地址
.text:00000001400AD951 shr rax, 39
; 将虚拟地址右移39位
.text:00000001400AD955 and eax, 1FFh
; 拿到pml4数组的下标
.text:00000001400AD95A test byte ptr [rdx+rax*8], 1
; 检测PML4T项p位
.text:00000001400AD95E jz short loc_1400AD9D3
; p=0 则直接返回false
.text:00000001400AD960 mov rax, rcx
.text:00000001400AD963 mov rdx, 0FFFFF6FB7DA00000h
.text:00000001400AD96D shr rax, 27
; 右移30位 ,再 乘 8 ,相当于右移27位
.text:00000001400AD971 and eax, 1FFFF8h
; 8字节对齐 得到PDPT的偏移
.text:00000001400AD976 test byte ptr [rax+rdx], 1
; 检测PDPT项的p位 rax+rdx=PDPT的首地址
.text:00000001400AD97A jz short loc_1400AD9D3
.text:00000001400AD97C mov rdx, -0FFFFF6FB40000000h
.text:00000001400AD986 mov rax, rcx
.text:00000001400AD989 shr rax, 18
; 右移21位,再乘8 ,相当于右移18位
.text:00000001400AD98D and eax, 3FFFFFF8h
;得到PDT的偏移
.text:00000001400AD992 sub rax, rdx
; rax = rax + 0FFFFF6FB40000000h
.text:00000001400AD995 mov rdx, [rax]
;此时rax指向PDT中的 某一项
.text:00000001400AD998 test dl, 1
; 检测PDT项p位
.text:00000001400AD99B jz short loc_1400AD9D3
.text:00000001400AD99D test dl, dl
.text:00000001400AD99F js short loc_1400AD9D6
; 是否开启PSE,是的话直接返回真
.text:00000001400AD9A1 shr rcx, 9
; 右移12位,再乘 8 ,相当于右移9位
.text:00000001400AD9A5 mov rax, 7FFFFFFFF8h ; 8字节对齐
.text:00000001400AD9AF and rcx, rax
.text:00000001400AD9B2 mov rax, -0FFFFF68000000000h
.text:00000001400AD9BC sub rcx, rax
.text:00000001400AD9BF mov rax, [rcx]
;此时RCX指向PT的的某一项
.text:00000001400AD9C2 test al, 1
.text:00000001400AD9C4 jz short loc_1400AD9D3
; 检测PT项的P位
.text:00000001400AD9C6 mov r8b, 80h
.text:00000001400AD9C9 and al, r8b
.text:00000001400AD9CC cmp al, r8b
.text:00000001400AD9CF setnz al
; 检测PT项的PAT位是否存在,不存在返回真
.text:00000001400AD9D2 retn
.text:00000001400AD9D3 ; ---------------------------------------------------------------------------
.text:00000001400AD9D3
.text:00000001400AD9D3 loc_1400AD9D3:
.text:00000001400AD9D3
.text:00000001400AD9D3 xor al, al
.text:00000001400AD9D5 retn
.text:00000001400AD9D6 ; ---------------------------------------------------------------------------
.text:00000001400AD9D6
.text:00000001400AD9D6 loc_1400AD9D6: _
.text:00000001400AD9D6 mov al, 1
.text:00000001400AD9D8 retn
.text:00000001400AD9D8 _MmIsAddressValid endp
可以看出,函数也是将线性地址进行了拆分,取得表中每一项的值后,依次进行判断 ,主要是针对P位
将以上代码做一个等价还原后如下:
bool _MmIsAddressValid(void *pAddress)
{
BYTE* pMl4e = (BYTE *)0x0FFFFF6FB7DBED000;
BYTE* pDPTE = (BYTE *)0x0FFFFF6FB7DA00000;
BYTE* pDE = (BYTE *)0x0FFFFF6FB40000000;
BYTE* pTE = (BYTE *)0x0FFFFF68000000000;
BYTE* curItem;
ULONG_PTR ulAddress = (ULONG_PTR)pAddress;
ulAddress = ((LONG_PTR)ulAddress>>48) + 1;
if (ulAddress <= 1) //只要是合法地址,必然不会超过1
{
ulAddress = (ULONG_PTR)pAddress;
ulAddress = ulAddress >> 39;
ulAddress = ulAddress & 0x1ff;
if ((pMl4e[ulAddress * 8] & 1) == 0) //检测pMl4T项的P位
return false;
ulAddress = (ULONG_PTR)pAddress;
ulAddress = (ulAddress >> 30) << 3;
ulAddress = ulAddress & 0x1FFFF8;
if ((pDPTE[ulAddress] & 1) == 0) //检测PDPT项的P位
return false;
ulAddress = (ULONG_PTR)pAddress;
ulAddress = (ulAddress >> 21) << 3;
ulAddress = ulAddress & 0x3FFFFFF8;
curItem = pDE + ulAddress;
if ((*(LONG_PTR*)curItem & 1) == 0) //检测PDT项的P位
return false;
if ((*(LONG_PTR*)curItem & 0xff) >= 0) //PDT的PSE被置位,直接返回TRUE (2mb分页)
{
ulAddress = (ULONG_PTR)pAddress;
ulAddress = (ulAddress >> 12) << 3;
ulAddress = ulAddress & 0x7FFFFFFFF8;
curItem = pTE + ulAddress;
if ((*(LONG_PTR*)curItem & 1) == 0) //检测PT项的P位
return false;
char itemTmp = (char)*(LONG_PTR*)curItem;
itemTmp = itemTmp & 0x80;
return (itemTmp == (char)0x80) ? false : true; //检测PT项的PAT位
}else
return true;
}
return false;
}
jpg 改 rar
X64下MmIsAddressValid的逆向及内存寻址解析的更多相关文章
- x64 结构体系下的内存寻址
欢迎转载,转载请注明出处:http://www.cnblogs.com/lanrenxinxin/p/4735027.html 在阅读NewBluePill源码的时候,看内存的那一块简直头疼,全是x6 ...
- X64下的虚拟地址到物理地址的转换
https://bbs.pediy.com/thread-203391.htm 早就知道传上来排版会全乱掉,把pdf直接传上来吧 x64结构体系寻址.pdf 发现安大的关于x86启用PAE下的虚拟 ...
- Linux内存寻址之分页机制
在上一篇文章Linux内存寻址之分段机制中,我们了解逻辑地址通过分段机制转换为线性地址的过程.下面,我们就来看看更加重要和复杂的分页机制. 分页机制在段机制之后进行,以完成线性—物理地址的转换过程.段 ...
- Linux内存寻址之分段机制
前言 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为自己对分段分页机制已经理解了,结果发现其实是一知半解.于是,查找了很多资料,最终理顺了内存寻址的知识. ...
- 如何打开Windows Server 2003 内存寻址扩展
本文介绍了如何在系统内存大于4G的情况下,让windows2003 Advanced Server支持大内存的方法: 由于Windows2003 32bit是32位操作系统,当服务器配备内存高达4G时 ...
- [原创]MinHook测试与分析(x64下 E9,EB,CALL指令测试,且逆推测试微软热补丁)
依稀记得第一次接触Hook的概念是在周伟民先生的书中-><<多任务下的数据结构与算法>>,当时觉得Hook很奇妙,有机会要学习到,正好近段日子找来了MiniHook,就一 ...
- Linux内核源码分析 day01——内存寻址
前言 Linux内核源码分析 Antz系统编写已经开始了内核部分了,在编写时同时也参考学习一点Linux内核知识. 自制Antz操作系统 一个自制的操作系统,Antz .半图形化半命令式系统,同时嵌入 ...
- 80x86的内存寻址机制
80x86的内存寻址机制 80386处理器的工作模式: 模式. 模式之间可以相互转换,而模式之间不可以相互转换. DOS系统运行于实模式下,Windows系统运行与保护模式下. 实模式: 80386处 ...
- Linux内存寻址之分段机制及分页机制【转】
前言 本文涉及的硬件平台是X86,如果是其他平台的话,如ARM,是会使用到MMU,但是没有使用到分段机制: 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为 ...
随机推荐
- hdu1520
基本的树形dp #include <cstring> #include <cstdio> #include <vector> using namespace std ...
- K3中添加的一条新数据,其在数据库中的位置
最近研究将K3系统与生产管理系统结合起来,减少工作量,但如何确定其各自后台数据库的构成,其对应数据各自位于那张表内,总结了一下: 1.从百度搜索,查看表结构,然后找到目标表 另:K3数据库中单独 ...
- 如何让ListView的item不可点击
原文链接:http://blog.csdn.net/zhangfei_jiayou/article/details/6972752 1. 如果是listView的id是使用系统默认的id,如下, 则可 ...
- 【python】mysqlDB转xml中的编码问题
背景:有mysql数据库,将数据从数据库中读取,并存储到xml中 采用了MySQLdb和lxml两个库 具体编码处理过程如下: . 指定mysql的编码方式 .取数据库data->判断data类 ...
- 【linux】linux脚本中#!/bin/sh的含义
来源:百度知道 #! /bin/sh 是指此脚本使用,/bin/sh来解释执行,#!是特殊的表示符,其后面根的是此解释此脚本的shell的路径.
- Redis事件管理(二)
Redis的定时器是自己实现的,不是很复杂.说说具体的实现吧. 定时器的存储维护采用的是普通的单向链表结构,具体节点定义为: /*时间定时器结构体*/ typedef struct aeTimeEve ...
- 《孙子算经》之"物不知数"题:中国剩余定理
1.<孙子算经>之"物不知数"题 今有物不知其数,三三数之剩二,五五数之剩七,七七数之剩二,问物几何? 2.中国剩余定理 定义: 设 a,b,m 都是整数. 如果 m ...
- 学习hibernate @Entity该导入哪个包
1.在@Entity时很容易顺手导入@org.hibernate.annotations.Entity这个包,结果导致了异常.其实应该导入的是@javax.persistence.Entity Alw ...
- jquery学习笔记----ajax使用
一.load() 加载页面数据 load(url,[data],[callback]) url:加载的页面地址,[data]传送的数据,[callback]加载完成时回调函数. 设计一个load.ht ...
- Linux常用的日志分析命令与工具
>>基础命令 操作 命令 说明 查看文件的内容 cat -n access.log -n显示行号 分页显示文件 more access.log Enter下一行,空格下一页,F下一屏,B上 ...