首先CR3是什么,CR3是一个寄存器,该寄存器内保存有页目录表物理地址(PDBR地址),其实CR3内部存放的就是页目录表的内存基地址,运用CR3切换可实现对特定进程内存地址的强制读写操作,此类读写属于有痕读写,多数驱动保护都会将这个地址改为无效,此时CR3读写就失效了,当然如果能找到CR3的正确地址,此方式也是靠谱的一种读写机制。

在读写进程之前需要先找到进程的PEPROCESS结构,查找结构的方法也很简单,依次遍历进程并对比进程名称即可得到。

#include <ntifs.h>
#include <windef.h>
#include <intrin.h> NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);
NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); // 定义全局EProcess结构
PEPROCESS Global_Peprocess = NULL; // 根据进程名获得EPROCESS结构
NTSTATUS GetProcessObjectByName(char *name)
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
SIZE_T i; __try
{
for (i = 100; i<20000; i += 4)
{
NTSTATUS st;
PEPROCESS ep;
st = PsLookupProcessByProcessId((HANDLE)i, &ep);
if (NT_SUCCESS(st))
{
char *pn = PsGetProcessImageFileName(ep);
if (_stricmp(pn, name) == 0)
{
Global_Peprocess = ep;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return Status;
}
return Status;
} VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n"); NTSTATUS nt = GetProcessObjectByName("Tutorial-i386.exe"); if (NT_SUCCESS(nt))
{
DbgPrint("[+] eprocess = %x \n", Global_Peprocess);
} Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

以打开Tutorial-i386.exe为例,打开后即可返回他的Proces,当然也可以直接传入进程PID同样可以得到进程Process结构地址。

// 根据PID打开进程
PEPROCESS Peprocess = NULL;
DWORD PID = 6672;
NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Peprocess);

通过CR3读取内存实现代码如下,我们读取Tutorial-i386.exe里面的0x0009EDC8这段内存,读出长度是4字节,代码如下。

#include <ntifs.h>
#include <windef.h>
#include <intrin.h> #define DIRECTORY_TABLE_BASE 0x028 #pragma intrinsic(_disable)
#pragma intrinsic(_enable) NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);
NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); // 关闭写保护
KIRQL Open()
{
KIRQL irql = KeRaiseIrqlToDpcLevel();
UINT64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
return irql;
} // 开启写保护
void Close(KIRQL irql)
{
UINT64 cr0 = __readcr0();
cr0 |= 0x10000;
_enable();
__writecr0(cr0);
KeLowerIrql(irql);
} // 检查内存
ULONG64 CheckAddressVal(PVOID p)
{
if (MmIsAddressValid(p) == FALSE)
return 0;
return *(PULONG64)p;
} // CR3 寄存器读内存
BOOLEAN CR3_ReadProcessMemory(IN PEPROCESS Process, IN PVOID Address, IN UINT32 Length, OUT PVOID Buffer)
{
ULONG64 pDTB = 0, OldCr3 = 0, vAddr = 0;
pDTB = CheckAddressVal((UCHAR*)Process + DIRECTORY_TABLE_BASE);
if (pDTB == 0)
{
return FALSE;
} _disable();
OldCr3 = __readcr3();
__writecr3(pDTB);
_enable(); if (MmIsAddressValid(Address))
{
RtlCopyMemory(Buffer, Address, Length);
DbgPrint("读入数据: %ld", *(PDWORD)Buffer);
return TRUE;
} _disable();
__writecr3(OldCr3);
_enable();
return FALSE;
} VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n"); // 根据PID打开进程
PEPROCESS Peprocess = NULL;
DWORD PID = 6672;
NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Peprocess); DWORD buffer = 0; BOOLEAN bl = CR3_ReadProcessMemory(Peprocess, (PVOID)0x0009EDC8, 4, &buffer); DbgPrint("readbuf = %x \n", buffer);
DbgPrint("readbuf = %d \n", buffer); Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

读出后输出效果如下:

写出内存与读取基本一致,代码如下。

#include <ntifs.h>
#include <windef.h>
#include <intrin.h> #define DIRECTORY_TABLE_BASE 0x028 #pragma intrinsic(_disable)
#pragma intrinsic(_enable) NTKERNELAPI NTSTATUS PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);
NTKERNELAPI CHAR* PsGetProcessImageFileName(PEPROCESS Process); // 关闭写保护
KIRQL Open()
{
KIRQL irql = KeRaiseIrqlToDpcLevel();
UINT64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
return irql;
} // 开启写保护
void Close(KIRQL irql)
{
UINT64 cr0 = __readcr0();
cr0 |= 0x10000;
_enable();
__writecr0(cr0);
KeLowerIrql(irql);
} // 检查内存
ULONG64 CheckAddressVal(PVOID p)
{
if (MmIsAddressValid(p) == FALSE)
return 0;
return *(PULONG64)p;
} // CR3 寄存器写内存
BOOLEAN CR3_WriteProcessMemory(IN PEPROCESS Process, IN PVOID Address, IN UINT32 Length, IN PVOID Buffer)
{
ULONG64 pDTB = 0, OldCr3 = 0, vAddr = 0; // 检查内存
pDTB = CheckAddressVal((UCHAR*)Process + DIRECTORY_TABLE_BASE);
if (pDTB == 0)
{
return FALSE;
} _disable(); // 读取CR3
OldCr3 = __readcr3(); // 写CR3
__writecr3(pDTB);
_enable(); // 验证并拷贝内存
if (MmIsAddressValid(Address))
{
RtlCopyMemory(Address, Buffer, Length);
return TRUE;
}
_disable(); // 恢复CR3
__writecr3(OldCr3);
_enable();
return FALSE;
} VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
} NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark \n"); // 根据PID打开进程
PEPROCESS Peprocess = NULL;
DWORD PID = 6672;
NTSTATUS nt = PsLookupProcessByProcessId((HANDLE)PID, &Peprocess); DWORD buffer = 999; BOOLEAN bl = CR3_WriteProcessMemory(Peprocess, (PVOID)0x0009EDC8, 4, &buffer);
DbgPrint("写出状态: %d \n", bl); Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

写出后效果如下:

至于进程将CR3改掉了读取不到该寄存器该如何处理,这里我找到了一段参考代码,可以实现寻找CR3地址这个功能。

#include <ntddk.h>
#include <ntstrsafe.h>
#include <windef.h>
#include <intrin.h> #pragma pack(push, 1) typedef struct _IDTR // IDT基址
{
USHORT limit; // 范围 占8位
ULONG64 base; // 基地址 占32位 _IDT_ENTRY类型指针
}IDTR, *PIDTR; typedef union _IDT_ENTRY
{
struct kidt
{
USHORT OffsetLow;
USHORT Selector;
USHORT IstIndex : 3;
USHORT Reserved0 : 5;
USHORT Type : 5;
USHORT Dpl : 2;
USHORT Present : 1;
USHORT OffsetMiddle;
ULONG OffsetHigh;
ULONG Reserved1;
}idt;
UINT64 Alignment;
} IDT_ENTRY, *PIDT_ENTRY; #pragma pack(pop) // 输出调试内容
void DebugPrint(const char* fmt, ...)
{
UNREFERENCED_PARAMETER(fmt);
va_list ap;
va_start(ap, fmt);
vDbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, fmt, ap);
va_end(ap);
return;
} // 获取IDT表地址
ULONG64 GetIdtAddr(ULONG64 pIdtBaseAddr, UCHAR pIndex)
{
PIDT_ENTRY Pidt_info = (PIDT_ENTRY)(pIdtBaseAddr);
Pidt_info += pIndex;
ULONG64 vCurrentAddr = 0;
ULONG64 vCurrentHighAddr = 0;
vCurrentAddr = Pidt_info->idt.OffsetMiddle;
vCurrentAddr = vCurrentAddr << 16;
vCurrentAddr += Pidt_info->idt.OffsetLow; vCurrentHighAddr = Pidt_info->idt.OffsetHigh;
vCurrentHighAddr = vCurrentHighAddr << 32;
vCurrentAddr += vCurrentHighAddr;
return vCurrentAddr;
} VOID UnLoadDriver()
{ } NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT pPDriverObj, _In_ PUNICODE_STRING pRegistryPath)
{
UNREFERENCED_PARAMETER(pRegistryPath);
pPDriverObj->DriverUnload = (PDRIVER_UNLOAD)UnLoadDriver; /**
TP版KiPageFault
fffff880`09f54000 50 push rax
// 这里实际上是真实处理函数的地址 需要 & 0xFFFFFFFFFFF00000
fffff880`09f54001 48b87830ce0980f8ffff mov rax,0FFFFF88009CE3078h
fffff880`09f5400b 4883ec08 sub rsp,8
fffff880`09f5400f 48890424 mov qword ptr [rsp],rax
fffff880`09f54013 48311424 xor qword ptr [rsp],rdx
fffff880`09f54017 e810000000 call fffff880`09f5402c
fffff880`09f5401c 896eff mov dword ptr [rsi-1],ebp
fffff880`09f5401f 230500000089 and eax,dword ptr [fffff87f`92f54025]
**/
//得到TP KiPageFault地址
// _IDTR vContent;
// __sidt(&vContent);
ULONG64 vTpKiPageFault = GetIdtAddr(vContent.base, 0xE); //得到TP 动态内存起始值
ULONG64 vTpMemory = *(PULONG64)(vTpKiPageFault + 0x3) & 0xFFFFFFFFFFF00000; //得到TP KiPageFault真实处理函数
ULONG64 vTpKiPageFaultFuncAddr = vTpMemory + 0x4CE7C; if (MmIsAddressValid((PVOID)vTpKiPageFaultFuncAddr))
{//真实处理函数有效 //得到TP数据对象基地址
ULONG64 vTpDataObjectBase = *(PULONG)(vTpMemory + 0x1738B) + vTpMemory + 0x1738F; if (MmIsAddressValid((PVOID)vTpDataObjectBase))
{//基地址有效 //得到TP 用来保存真实CR3 保存当前所属进程ID 的对象
ULONG64 vTpDataObject = *(PULONG64)vTpDataObjectBase; DebugPrint("数据对象:0x%016llx, 真实CR3:0x%016llx, 所属进程ID:%d\n", vTpDataObject, *(PULONG64)(vTpDataObject + 0x70), *(PULONG)(vTpDataObject + 0x18));
}
else
DebugPrint("vTpDataObjectBase无法读取:0x%016llx\n", vTpDataObjectBase);
}
else
DebugPrint("vTpKiPageFaultFuncAddr无法读取:0x%016llx\n", vTpKiPageFaultFuncAddr); return STATUS_SUCCESS;
}

驱动开发:内核CR3切换读写内存的更多相关文章

  1. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  2. 驱动开发:内核运用LoadImage屏蔽驱动

    在笔者上一篇文章<驱动开发:内核监视LoadImage映像回调>中LyShark简单介绍了如何通过PsSetLoadImageNotifyRoutine函数注册回调来监视驱动模块的加载,注 ...

  3. 《Windows内核安全与驱动开发》 3.2 内存与链表

    <Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发> 3.2 内存与链表 1. 尝试生成一个链表头并将其初始化. 2. 尝试向内存 ...

  4. Linux驱动开发常用调试工具---之内存读写工具devmem和devkmem【转】

    转自:https://blog.csdn.net/gatieme/article/details/50964903 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...

  5. 驱动开发:内核R3与R0内存映射拷贝

    在上一篇博文<驱动开发:内核通过PEB得到进程参数>中我们通过使用KeStackAttachProcess附加进程的方式得到了该进程的PEB结构信息,本篇文章同样需要使用进程附加功能,但这 ...

  6. Windows内核安全与驱动开发

    这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...

  7. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html   IT168 技术文档]在开始步入L ...

  8. 《Windows内核安全与驱动开发》 7.1&7.2&7.3 串口的过滤

    <Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发> 7.1&7.2&7.3 串口的过滤 一.设备绑定的内核API ...

  9. Windows驱动开发-设备读写方式

    设备读写方式共三种: 方式 Flag 特点 缓冲区方式读写 DO_BUFFERED_IO I/O管理器先创建一个与用户模式数据缓冲区大小相等的系统缓冲区.而你的驱动程序将使用这个系统缓冲区工作.I/O ...

随机推荐

  1. surging作者出具压测结果

    前言 首先回应下@wen-wen 所贴的压测报告,我也把我和客户压测碰到的问题,和压测结果贴出来,这个结果是由客户提供的.不会有任何的舞弊手脚问题 问题一:Task.Run慎用 首先在最新的社区版本已 ...

  2. 基于串口校时的数字钟设计(verilog实现)

    任务: 电路图设计: 设计: 模块1:1.先设计一个计数时钟,为了仿真方便,这里把1000ns当作1s. 创建一个计数器second_lim,当计数到1000/20时清零,即1s. 秒显示器secon ...

  3. Hbase学习(三)过滤器 java API

    Hbase学习(三)过滤器 HBase 的基本 API,包括增.删.改.查等. 增.删都是相对简单的操作,与传统的 RDBMS 相比,这里的查询操作略显苍白,只能根据特性的行键进行查询(Get)或者根 ...

  4. 【实操填坑】在树莓派上编译 EtherCAT IgH Master 主站程序

    官网下载地址:https://etherlab.org/download/ethercat/  (可list查看文件列表)https://etherlab.org/download/ethercat/ ...

  5. 【每天学一点-01】 在SpringBoot项目中使用Swagger2

    今天在做毕设的时候,发现在前后端分离的情况下,去调用接口数据时很不方便,然后回想过去,和同学一起做项目的时候,他负责后端,我负责前端,当时调用他的弄好的接口可以说是非常方便,主要是可以通过UI页面直接 ...

  6. input函数的高级使用

    经典的a+b问题终于重出江湖了 a=input('a = ') b=input('b = ') print(a+b)//error,因为此时ab是字符串类型,其加号起到的是连接的作用 所以这就是类型转 ...

  7. 向docker镜像中传递变量的两种方式

    测试用到的python文件: #!/usr/bin/env python3 #conding: utf-8 from http.server import HTTPServer, BaseHTTPRe ...

  8. vscode problem

    1.Inconsistent use of tabs and spaces in indentation 原因:tab和空格键不能同时使用 vs code按住ctrl + p,输入以下内容 >c ...

  9. 理解JavaScript中的window对象

    前言 每个JavaScript环境都有一个全局对象(global object).在全局范围内创建的任何变量实际上都是这个对象的属性,而任何函数都是它的方法.在浏览器环境中,全局对象是window对象 ...

  10. 在阿里云Centos7.6上面部署基于Redis的分布式爬虫Scrapy-Redis

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_83 Scrapy是一个比较好用的Python爬虫框架,你只需要编写几个组件就可以实现网页数据的爬取.但是当我们要爬取的页面非常多的 ...