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

来源: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 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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...