驅動開發:核心中實現Dump程序轉儲

2022-10-11 21:12:15

多數ARK反核心工具中都存在驅動級別的記憶體轉存功能,該功能可以將應用層中執行程序的記憶體映象轉存到特定目錄下,記憶體轉存功能在應對加殼程式的分析尤為重要,當程序在記憶體中解碼後,我們可以很容易的將記憶體映象匯出,從而更好的對樣本進行分析,當然某些加密殼可能無效但絕大多數情況下是可以被轉存的。

在上一篇文章《驅動開發:核心R3與R0記憶體對映拷貝》介紹了一種方式SafeCopyMemory_R3_to_R0可以將應用層程序的記憶體空間對映到核心中,要實現記憶體轉儲功能我們還是需要使用這個對映函數,只是需要在此函數上增加一些功能而已。

在實現轉存之前,需要得到兩個東西,程序內模組基地址以及模組長度這兩個引數是必不可少的,至於核心中如何得到指定程序的模組資料,在很早之前的文章《驅動開發:核心中列舉進執行緒與模組》中有詳細的參考方法,這裡就在此基礎之上實現一個簡單的程序模組遍歷功能。

如下程式碼中使用的就是列舉程序PEB結構得到更多引數的具體實現,如果不懂得可以研讀《驅動開發:核心通過PEB得到程序引數》這篇文章此處不再贅述。

#include <ntddk.h>
#include <windef.h>

// 宣告結構體
typedef struct _KAPC_STATE
{
	LIST_ENTRY ApcListHead[2];
	PKPROCESS Process;
	UCHAR KernelApcInProgress;
	UCHAR KernelApcPending;
	UCHAR UserApcPending;
} KAPC_STATE, *PKAPC_STATE;

typedef struct _LDR_DATA_TABLE_ENTRY
{
	LIST_ENTRY64	InLoadOrderLinks;
	LIST_ENTRY64	InMemoryOrderLinks;
	LIST_ENTRY64	InInitializationOrderLinks;
	PVOID			DllBase;
	PVOID			EntryPoint;
	ULONG			SizeOfImage;
	UNICODE_STRING	FullDllName;
	UNICODE_STRING 	BaseDllName;
	ULONG			Flags;
	USHORT			LoadCount;
	USHORT			TlsIndex;
	PVOID			SectionPointer;
	ULONG			CheckSum;
	PVOID			LoadedImports;
	PVOID			EntryPointActivationContext;
	PVOID			PatchInformation;
	LIST_ENTRY64	ForwarderLinks;
	LIST_ENTRY64	ServiceTagLinks;
	LIST_ENTRY64	StaticLinks;
	PVOID			ContextInformation;
	ULONG64			OriginalBase;
	LARGE_INTEGER	LoadTime;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

// 偏移地址
ULONG64 LdrInPebOffset = 0x018;		//peb.ldr
ULONG64 ModListInPebOffset = 0x010;	//peb.ldr.InLoadOrderModuleList

// 宣告API
NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);
NTKERNELAPI PPEB PsGetProcessPeb(PEPROCESS Process);
NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);

// 根據程序ID返回程序EPROCESS,失敗返回NULL
PEPROCESS LookupProcess(HANDLE Pid)
{
	PEPROCESS eprocess = NULL;
	if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess)))
		return eprocess;
	else
		return NULL;
}

// 列舉指定程序的模組
// By: LyShark.com
VOID EnumModule(PEPROCESS Process)
{
	SIZE_T Peb = 0;
	SIZE_T Ldr = 0;
	PLIST_ENTRY ModListHead = 0;
	PLIST_ENTRY Module = 0;
	ANSI_STRING AnsiString;
	KAPC_STATE ks;
	
	// EPROCESS地址無效則退出
	if (!MmIsAddressValid(Process))
		return;
	
	// 獲取PEB地址
	Peb = (SIZE_T)PsGetProcessPeb(Process);
	
	// PEB地址無效則退出
	if (!Peb)
		return;
	
	// 依附程序
	KeStackAttachProcess(Process, &ks);
	__try
	{
		// 獲得LDR地址
		Ldr = Peb + (SIZE_T)LdrInPebOffset;
		// 測試是否可讀,不可讀則丟擲異常退出
		ProbeForRead((CONST PVOID)Ldr, 8, 8);
		// 獲得連結串列頭
		ModListHead = (PLIST_ENTRY)(*(PULONG64)Ldr + ModListInPebOffset);
		// 再次測試可讀性
		ProbeForRead((CONST PVOID)ModListHead, 8, 8);
		// 獲得第一個模組的資訊
		Module = ModListHead->Flink;
		
		while (ModListHead != Module)
		{
			//列印資訊:基址、大小、DLL路徑
			DbgPrint("模組基址 = %p | 大小 = %ld | 模組名 = %wZ | 完整路徑= %wZ \n",
				(PVOID)(((PLDR_DATA_TABLE_ENTRY)Module)->DllBase),
				(ULONG)(((PLDR_DATA_TABLE_ENTRY)Module)->SizeOfImage),
				&(((PLDR_DATA_TABLE_ENTRY)Module)->BaseDllName),
				&(((PLDR_DATA_TABLE_ENTRY)Module)->FullDllName)
				);
			Module = Module->Flink;
			
			// 測試下一個模組資訊的可讀性
			ProbeForRead((CONST PVOID)Module, 80, 8);
		}
	}
	__except (EXCEPTION_EXECUTE_HANDLER){ ; }
	
	// 取消依附程序
	KeUnstackDetachProcess(&ks);
}

VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{

}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \n");

	ULONG i = 0;
	PEPROCESS eproc = NULL;
	for (i = 4; i<100000000; i = i + 4)
	{
		eproc = LookupProcess((HANDLE)i);
		if (eproc != NULL)
		{
			ObDereferenceObject(eproc);
			if (strstr(PsGetProcessImageFileName(eproc), "lyshark.exe") != NULL)
			{
				EnumModule(eproc);
			}
		}
	}

	DriverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

如上我們指定獲取應用層lyshark.exe程序的模組資訊,並可得到以下輸出效果:

上篇文章中的程式碼就不再囉嗦了,這裡只給出記憶體轉存的核心程式碼,如下程式碼:

  • RtlInitUnicodeString 用於初始化轉存後的名字字串
  • ZwCreateFile 核心中建立檔案到應用層
  • ZwWriteFile 將檔案寫出到檔案
  • ZwClose 最後是關閉檔案並釋放堆空間

很簡單只是利用了SafeCopyMemory_R3_to_R0將程序記憶體讀取到緩衝區內,並將緩衝區寫出到C槽目錄下。

// 程序記憶體拷貝函數
// By: LyShark.com
NTSTATUS ProcessDumps(PEPROCESS pEprocess, ULONG_PTR nBase, ULONG nSize)
{
	BOOLEAN bAttach = FALSE;
	KAPC_STATE ks = { 0 };
	PVOID pBuffer = NULL;
	NTSTATUS status = STATUS_UNSUCCESSFUL;

	if (nSize == 0 || pEprocess == NULL)
	{
		return status;
	}

	pBuffer = ExAllocatePoolWithTag(PagedPool, nSize, 'lysh');
	if (!pBuffer)
	{
		return status;
	}

	memset(pBuffer, 0, nSize);

	if (pEprocess != IoGetCurrentProcess())
	{
		KeStackAttachProcess(pEprocess, &ks);
		bAttach = TRUE;
	}

	status = SafeCopyMemory_R3_to_R0(nBase, (ULONG_PTR)pBuffer, nSize);

	if (bAttach)
	{
		KeUnstackDetachProcess(&ks);
		bAttach = FALSE;
	}

	OBJECT_ATTRIBUTES object;
	IO_STATUS_BLOCK io;
	HANDLE hFile;
	UNICODE_STRING log;

	// 匯出檔名稱
	RtlInitUnicodeString(&log, L"\\??\\C:\\lyshark_dumps.exe");
	InitializeObjectAttributes(&object, &log, OBJ_CASE_INSENSITIVE, NULL, NULL);

	status = ZwCreateFile(&hFile,
		GENERIC_WRITE,
		&object,
		&io,
		NULL,
		FILE_ATTRIBUTE_NORMAL,
		FILE_SHARE_WRITE,
		FILE_OPEN_IF,
		FILE_SYNCHRONOUS_IO_NONALERT,
		NULL,
		0);

	if (!NT_SUCCESS(status))
	{
		DbgPrint("開啟檔案錯誤 \n");
		return STATUS_SUCCESS;
	}

	ZwWriteFile(hFile, NULL, NULL, NULL, &io, pBuffer, nSize, NULL, NULL);
	DbgPrint("寫出位元組數: %d \n", io.Information);
	DbgPrint("[*] LyShark.exe 已轉存");
	ZwClose(hFile);

	if (pBuffer)
	{
		ExFreePoolWithTag(pBuffer, 'lysh');
		pBuffer = NULL;
	}

	return status;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
	DbgPrint(("Uninstall Driver Is OK \n"));
}

// lyshark.com
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
	DbgPrint("hello lyshark.com \n");

	NTSTATUS ntStatus;
	PEPROCESS pCurProcess = NULL;

	__try
	{
		ntStatus = PsLookupProcessByProcessId((HANDLE)272, &pCurProcess);
		if (NT_SUCCESS(ntStatus))
		{
			// 設定基地址以及長度
			ntStatus = ProcessDumps(pCurProcess, 0x140000000, 1024);
			ObDereferenceObject(pCurProcess);
		}
	}
	__except (1)
	{
		ntStatus = GetExceptionCode();
	}

	Driver->DriverUnload = UnDriver;
	return STATUS_SUCCESS;
}

轉存後效果如下所示:

至於匯出的程序無法執行只是沒有修復而已(後期會講),可以開啟看看是沒錯的。