驅動開發:內核CR3切換讀寫記憶體

来源:https://www.cnblogs.com/LyShark/archive/2022/09/25/16727379.html
-Advertisement-
Play Games

首先CR3是什麼,CR3是一個寄存器,該寄存器內保存有頁目錄表物理地址(PDBR地址),其實CR3內部存放的就是頁目錄表的記憶體基地址,運用CR3切換可實現對特定進程記憶體地址的強制讀寫操作,此類讀寫屬於有痕讀寫,多數驅動保護都會將這個地址改為無效,此時CR3讀寫就失效了,當然如果能找到CR3的正確地址... ...


首先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;
}
文章作者:lyshark (王瑞)
文章出處:https://www.cnblogs.com/LyShark/p/16727379.html
版權聲明:本博客文章與代碼均為學習時整理的筆記,文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!

轉載文章,請遵守《中華人民共和國著作權法》相關規定或遵守《署名CC BY-ND 4.0國際》禁止演繹規範,合理合規,攜帶原創出處轉載。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 來源:https://www.linuxmi.com/50-million-pc-linux.html 開源社區的一大勝利! 繼德國之後,中國現在想在 5000 萬台 PC 上拋棄 Windows 並運行 Linux! 如果您一直密切關註 Linux 新聞,您可能聽說過德國去年在超過 25000 台 ...
  • ###一、Scrapy 介紹 Scrapy是一個Python編寫的開源和協作的框架。起初是用於網路頁面抓取所設計的,使用它可以快速、簡單、可擴展的方式從網站中提取所需的數據。 Scrapy也是通用的網路爬蟲框架,爬蟲界的django(設計原則很像),可用於數據挖掘、監測和自動化測試、也可以應用在獲取 ...
  • 2022-09-25 首先,要安裝好虛擬環境,之後要切換到虛擬環境中,使用的命令 workon 創建好的虛擬環境的名稱 之後,創建一個Django項目使用的命令: django-admin startproject 項目名稱 進入到該項目的目錄下,創建一個子應用,使用的命令: python mana ...
  • 拉格朗日插值原理及實現(Python) 一. 前言 Lagrange插值是利用n次多項式來擬合**(n+1)個數據點**從而得到插值函數的方法。(註意n次多項式的定義是未知數最高次冪為n,但是多項式繫數有n+1個,因為還有個常數項) **Lagrange插值和Newton插值本質上相同,都是用(n- ...
  • ###一、背景知識 爬蟲的本質就是一個socket客戶端與服務端的通信過程,如果我們有多個url待爬取,只用一個線程且採用串列的方式執行,那隻能等待爬取一個結束後才能繼續下一個,效率會非常低。 需要強調的是:對於單線程下串列N個任務,並不完全等同於低效,如果這N個任務都是純計算的任務,那麼該線程對c ...
  • ##Invalid bound statement (not found)出現原因和解決方法 ###前言: 想必各位小伙伴在碼路上經常會碰到奇奇怪怪的事情,比如出現Invalid bound statement (not found),那今天我就來分析以下出現此問題的原因。 其實出現這個問題實質就是 ...
  • 二叉樹查找指定的節點 前序查找的思路 1.先判斷當前節點的no是否等於要查找的 2.如果是相等,則返回當前節點 3.如果不等,則判斷當前節點的左子節點是否為空,如果不為空,則遞歸前序查找 4.如果左遞歸前序查找,找到節點,則返回,否繼續判斷,當前的節點的右子節點是否為空,如果不為空,則繼續向右遞歸前 ...
  • 說明 onlyoffice為一款開源的office線上編輯組件,提供word/excel/ppt編輯保存操作 以下操作均基於centos8系統,officeonly鏡像版本7.1.2.23 鏡像下載地址:https://yunpan.360.cn/surl_y87CKKcPdY4 (提取碼:1f92 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...