驅動開發:內核實現進程彙編與反彙編

来源:https://www.cnblogs.com/LyShark/archive/2023/05/23/17145544.html
-Advertisement-
Play Games

在筆者上一篇文章`《驅動開發:內核MDL讀寫進程記憶體》`簡單介紹瞭如何通過MDL映射的方式實現進程讀寫操作,本章將通過如上案例實現遠程進程反彙編功能,此類功能也是ARK工具中最常見的功能之一,通常此類功能的實現分為兩部分,內核部分只負責讀寫位元組集,應用層部分則配合反彙編引擎對位元組集進行解碼,此處我們... ...


在筆者上一篇文章《驅動開發:內核MDL讀寫進程記憶體》簡單介紹瞭如何通過MDL映射的方式實現進程讀寫操作,本章將通過如上案例實現遠程進程反彙編功能,此類功能也是ARK工具中最常見的功能之一,通常此類功能的實現分為兩部分,內核部分只負責讀寫位元組集,應用層部分則配合反彙編引擎對位元組集進行解碼,此處我們將運用capstone引擎實現這個功能。

首先是實現驅動部分,驅動程式的實現是一成不變的,僅僅只是做一個讀寫功能即可,完整的代碼如下所示;

// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#include <windef.h>

#define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

#define DEVICENAME L"\\Device\\ReadWriteDevice"
#define SYMBOLNAME L"\\??\\ReadWriteSymbolName"

typedef struct
{
	DWORD pid;       // 進程PID
	UINT64 address;  // 讀寫地址
	DWORD size;      // 讀寫長度
	BYTE* data;      // 讀寫數據集
}ProcessData;

// MDL讀取封裝
BOOLEAN ReadProcessMemory(ProcessData* ProcessData)
{
	BOOLEAN bRet = TRUE;
	PEPROCESS process = NULL;

	// 將PID轉為EProcess
	PsLookupProcessByProcessId(ProcessData->pid, &process);
	if (process == NULL)
	{
		return FALSE;
	}

	BYTE* GetProcessData = NULL;
	__try
	{
		// 分配堆空間 NonPagedPool 非分頁記憶體
		GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
	}
	__except (1)
	{
		return FALSE;
	}

	KAPC_STATE stack = { 0 };
	// 附加到進程
	KeStackAttachProcess(process, &stack);

	__try
	{
		// 檢查進程記憶體是否可讀取
		ProbeForRead(ProcessData->address, ProcessData->size, 1);

		// 完成拷貝
		RtlCopyMemory(GetProcessData, ProcessData->address, ProcessData->size);
	}
	__except (1)
	{
		bRet = FALSE;
	}

	// 關閉引用
	ObDereferenceObject(process);

	// 解除附加
	KeUnstackDetachProcess(&stack);

	// 拷貝數據
	RtlCopyMemory(ProcessData->data, GetProcessData, ProcessData->size);

	// 釋放堆
	ExFreePool(GetProcessData);
	return bRet;
}

// MDL寫入封裝
BOOLEAN WriteProcessMemory(ProcessData* ProcessData)
{
	BOOLEAN bRet = TRUE;
	PEPROCESS process = NULL;

	// 將PID轉為EProcess
	PsLookupProcessByProcessId(ProcessData->pid, &process);
	if (process == NULL)
	{
		return FALSE;
	}

	BYTE* GetProcessData = NULL;
	__try
	{
		// 分配堆
		GetProcessData = ExAllocatePool(NonPagedPool, ProcessData->size);
	}
	__except (1)
	{
		return FALSE;
	}

	// 迴圈寫出
	for (int i = 0; i < ProcessData->size; i++)
	{
		GetProcessData[i] = ProcessData->data[i];
	}

	KAPC_STATE stack = { 0 };

	// 附加進程
	KeStackAttachProcess(process, &stack);

	// 分配MDL對象
	PMDL mdl = IoAllocateMdl(ProcessData->address, ProcessData->size, 0, 0, NULL);
	if (mdl == NULL)
	{
		return FALSE;
	}

	MmBuildMdlForNonPagedPool(mdl);

	BYTE* ChangeProcessData = NULL;

	__try
	{
		// 鎖定地址
		ChangeProcessData = MmMapLockedPages(mdl, KernelMode);

		// 開始拷貝
		RtlCopyMemory(ChangeProcessData, GetProcessData, ProcessData->size);
	}
	__except (1)
	{
		bRet = FALSE;
		goto END;
	}

	// 結束釋放MDL關閉引用取消附加
END:
	IoFreeMdl(mdl);
	ExFreePool(GetProcessData);
	KeUnstackDetachProcess(&stack);
	ObDereferenceObject(process);

	return bRet;
}

NTSTATUS DriverIrpCtl(PDEVICE_OBJECT device, PIRP pirp)
{
	PIO_STACK_LOCATION stack;
	stack = IoGetCurrentIrpStackLocation(pirp);
	ProcessData* ProcessData;

	switch (stack->MajorFunction)
	{

	case IRP_MJ_CREATE:
	{
		break;
	}

	case IRP_MJ_CLOSE:
	{
		break;
	}

	case IRP_MJ_DEVICE_CONTROL:
	{
		// 獲取應用層傳值
		ProcessData = pirp->AssociatedIrp.SystemBuffer;

		DbgPrint("進程ID: %d | 讀寫地址: %p | 讀寫長度: %d \n", ProcessData->pid, ProcessData->address, ProcessData->size);

		switch (stack->Parameters.DeviceIoControl.IoControlCode)
		{
		// 讀取函數
		case READ_PROCESS_CODE:
		{
			ReadProcessMemory(ProcessData);
			break;
		}
		// 寫入函數
		case WRITE_PROCESS_CODE:
		{
			WriteProcessMemory(ProcessData);
			break;
		}

		}

		pirp->IoStatus.Information = sizeof(ProcessData);
		break;
	}

	}

	pirp->IoStatus.Status = STATUS_SUCCESS;
	IoCompleteRequest(pirp, IO_NO_INCREMENT);
	return STATUS_SUCCESS;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	if (driver->DeviceObject)
	{
		UNICODE_STRING SymbolName;
		RtlInitUnicodeString(&SymbolName, SYMBOLNAME);

		// 刪除符號鏈接
		IoDeleteSymbolicLink(&SymbolName);
		IoDeleteDevice(driver->DeviceObject);
	}
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	NTSTATUS status = STATUS_SUCCESS;
	PDEVICE_OBJECT device = NULL;
	UNICODE_STRING DeviceName;

	DbgPrint("[LyShark] hello lyshark.com \n");

	// 初始化設備名
	RtlInitUnicodeString(&DeviceName, DEVICENAME);

	// 創建設備
	status = IoCreateDevice(Driver, sizeof(Driver->DriverExtension), &DeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &device);
	if (status == STATUS_SUCCESS)
	{
		UNICODE_STRING SymbolName;
		RtlInitUnicodeString(&SymbolName, SYMBOLNAME);

		// 創建符號鏈接
		status = IoCreateSymbolicLink(&SymbolName, &DeviceName);

		// 失敗則刪除設備
		if (status != STATUS_SUCCESS)
		{
			IoDeleteDevice(device);
		}
	}

	// 派遣函數初始化
	Driver->MajorFunction[IRP_MJ_CREATE] = DriverIrpCtl;
	Driver->MajorFunction[IRP_MJ_CLOSE] = DriverIrpCtl;
	Driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverIrpCtl;

	// 卸載驅動
	Driver->DriverUnload = UnDriver;

	return STATUS_SUCCESS;
}

上方的驅動程式很簡單關鍵部分已經做好了備註,此類驅動換湯不換藥沒啥難度,接下來才是本節課的重點,讓我們開始瞭解一下Capstone這款反彙編引擎吧,Capstone是一個輕量級的多平臺、多架構的反彙編框架。Capstone旨在成為安全社區中二進位分析和反彙編的終極反彙編引擎,該引擎支持多種平臺的反彙編,非常推薦使用。

這款反彙編引擎如果你想要使用它則第一步就是調用cs_open()官方對其的解釋是打開一個句柄,這個打開功能其中的參數如下所示;

  • 參數1:指定模式 CS_ARCH_X86 表示為Windows平臺
  • 參數2:執行位數 CS_MODE_32為32位模式,CS_MODE_64為64位
  • 參數3:打開後保存的句柄&dasm_handle

第二步也是最重要的一步,調用cs_disasm()反彙編函數,該函數的解釋如下所示;

  • 參數1:指定dasm_handle反彙編句柄
  • 參數2:指定你要反彙編的數據集或者是一個緩衝區
  • 參數3:指定你要反彙編的長度 64
  • 參數4:輸出的記憶體地址起始位置 0x401000
  • 參數5:預設填充為0
  • 參數6:用於輸出數據的一個指針

這兩個函數如果能搞明白,那麼如下反彙編完整代碼也就可以理解了。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>
#include <inttypes.h>
#include <capstone/capstone.h>

#pragma comment(lib,"capstone64.lib")

#define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

typedef struct
{
	DWORD pid;
	UINT64 address;
	DWORD size;
	BYTE* data;
}ProcessData;

int main(int argc, char* argv[])
{
	// 連接到驅動
	HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	ProcessData data;
	DWORD dwSize = 0;

	// 指定需要讀寫的進程
	data.pid = 6932;
	data.address = 0x401000;
	data.size = 64;

	// 讀取機器碼到BYTE位元組數組
	data.data = new BYTE[data.size];
	DeviceIoControl(handle, READ_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);
	for (int i = 0; i < data.size; i++)
	{
		printf("0x%02X ", data.data[i]);
	}

	printf("\n");

	// 開始反彙編
	csh dasm_handle;
	cs_insn *insn;
	size_t count;

	// 打開句柄
	if (cs_open(CS_ARCH_X86, CS_MODE_32, &dasm_handle) != CS_ERR_OK)
	{
		return 0;
	}

	// 反彙編代碼
	count = cs_disasm(dasm_handle, (unsigned char *)data.data, data.size, data.address, 0, &insn);

	if (count > 0)
	{
		size_t index;
		for (index = 0; index < count; index++)
		{
			/*
			for (int x = 0; x < insn[index].size; x++)
			{
				printf("機器碼: %d -> %02X \n", x, insn[index].bytes[x]);
			}
			*/

			printf("地址: 0x%"PRIx64" | 長度: %d 反彙編: %s %s \n", insn[index].address, insn[index].size, insn[index].mnemonic, insn[index].op_str);
		}
		cs_free(insn, count);
	}
	cs_close(&dasm_handle);

	getchar();
	CloseHandle(handle);
	return 0;
}

通過驅動載入工具載入WinDDK.sys然後在運行本程式,你會看到正確的輸出結果,反彙編當前位置處向下64位元組。

說完了反彙編接著就需要講解如何對記憶體進行彙編操作了,彙編引擎這裡採用了XEDParse該引擎小巧簡潔,著名的x64dbg就是在運用本引擎進行彙編替換的,本引擎的使用非常簡單,只需要向XEDParseAssemble()函數傳入一個規範的結構體即可完成轉換,完整代碼如下所示。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

extern "C"
{
#include "D:/XEDParse/XEDParse.h"
#pragma comment(lib, "D:/XEDParse/XEDParse_x64.lib")
}

using namespace std;

#define READ_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ALL_ACCESS)
#define WRITE_PROCESS_CODE CTL_CODE(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ALL_ACCESS)

typedef struct
{
	DWORD pid;
	UINT64 address;
	DWORD size;
	BYTE* data;
}ProcessData;

int main(int argc, char* argv[])
{
	// 連接到驅動
	HANDLE handle = CreateFileA("\\??\\ReadWriteSymbolName", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

	ProcessData data;
	DWORD dwSize = 0;

	// 指定需要讀寫的進程
	data.pid = 6932;
	data.address = 0x401000;
	data.size = 0;

	XEDPARSE xed = { 0 };
	xed.x64 = FALSE;

	// 輸入一條彙編指令並轉換
	scanf_s("%llx", &xed.cip);
	gets_s(xed.instr, XEDPARSE_MAXBUFSIZE);
	if (XEDPARSE_OK != XEDParseAssemble(&xed))
	{
		printf("指令錯誤: %s\n", xed.error);
	}

	// 生成堆
	data.data = new BYTE[xed.dest_size];

	// 設置長度
	data.size = xed.dest_size;

	for (size_t i = 0; i < xed.dest_size; i++)
	{
		// 替換到堆中
		printf("%02X ", xed.dest[i]);
		data.data[i] = xed.dest[i];
	}

	// 調用控制器,寫入到遠端記憶體
	DeviceIoControl(handle, WRITE_PROCESS_CODE, &data, sizeof(data), &data, sizeof(data), &dwSize, NULL);

	printf("[LyShark] 指令集已替換. \n");
	getchar();
	CloseHandle(handle);
	return 0;
}

通過驅動載入工具載入WinDDK.sys然後在運行本程式,你會看到正確的輸出結果,可打開反內核工具驗證是否改寫成功。

打開反內核工具,並切換到觀察是否寫入了一條mov eax,1的指令集機器碼,如下圖已經完美寫入。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目傳送門: >[【洛谷】P4710 [物理]平拋運動](https://www.luogu.com.cn/problem/P4710 "【洛谷】P4710 [物理]平拋運動") ## Step 1:前置芝士 您需要知道並瞭解以下芝士: 1. 數學: - 三角函數; 2. 物理: - 加速度公式; ...
  • 本系列前面講解了Spring的bean定義、bean實例化、bean初始化等生命周期階段。這些步驟使我們能夠瞭解bean從創建到準備好使用所經歷的過程。但是,除了這些步驟,bean的銷毀也是非常重要的一步。在本系列的最後,我們將深入探討bean的銷毀過程,包括在什麼情況下會發生銷毀、銷毀的順序以及如... ...
  • PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後上傳記錄, PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後上傳記錄, PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後上傳記錄, PHP 獲取無限級下級ID 無層級 非遞歸 刪除會員處有誤,修複後 ...
  • # Java IO流 ## 什麼是流? 概念:記憶體和存儲設備之間傳輸數據的通道。 數據藉助流傳輸。 流分類: - 按照方向:輸入流(將存儲設備中的內容讀入到記憶體中)和輸出流(將記憶體中的內容寫入到存儲設備中) - 按照單位:位元組流(以位元組為單位,可以讀寫所有數據)和字元流(以字元為單位,只能讀取文本數 ...
  • 本文使用的是巴法雲 你也可以使用其他的物聯網平臺 並且 也不一定是小愛 比如小度啊 等等其他的一下應該也是可以實現的 調到java裡面之後 剩下的事情大家就可以想幹嘛就幹嘛了 ...
  • ## 一、安裝laradock ### 1. 如果有laravel項目並使用git,可以用git submodule將laradock克隆到laravel根目錄,方便後續管理 ```git submodule add https://github.com/laradock/laradock.git` ...
  • #### 錯誤: 找不到或無法載入主類 jar ##### 問題描述: 在使用springboot框架對項目打包後,手動使用命令java -jar 包名啟動jar包,報錯:錯誤: 找不到或無法載入主類 jar。 網上找了各辦法,都是加maven插件,打成可執行jar包 ``` org.springf ...
  • [TOC](Nett的概念及體繫結構) # 第一章 Java網路編程 最早期的 Java API(java.net)只支持由本地系統套接字型檔提供的所謂的阻塞函數,像下麵的那樣 ```java //創建一個新的 ServerSocket,用以監聽指定埠上的連接請求 ServerSocket serv ...
一周排行
    -Advertisement-
    Play Games
  • 前言 微服務架構已經成為搭建高效、可擴展系統的關鍵技術之一,然而,現有許多微服務框架往往過於複雜,使得我們普通開發者難以快速上手並體驗到微服務帶了的便利。為瞭解決這一問題,於是作者精心打造了一款最接地氣的 .NET 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...