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

来源: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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...