多數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
程序的模組資訊,並可得到以下輸出效果:
上篇文章中的程式碼就不再囉嗦了,這裡只給出記憶體轉存的核心程式碼,如下程式碼:
很簡單只是利用了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;
}
轉存後效果如下所示:
至於匯出的程序無法執行只是沒有修復而已(後期會講),可以開啟看看是沒錯的。