目录
- 正文
- 特征码字符串解析与扫描实现
- UtilLySharkSearchPattern如何定位特征
- RtlImageNtHeader对其PE头部解析
正文
在笔者上一篇文章《驱动开发:内核特征码搜索函数封装》中为了定位特征的方便我们封装实现了一个可以传入数组实现的SearchSpecialCode定位函数,该定位函数其实还不能算的上简单,本章LyShark将对特征码定位进行简化,让定位变得更简单,并运用定位代码实现扫描内核PE的.text代码段,并从代码段中得到某个特征所在内存位置。
老样子为了后续教程能够继续,先来定义一个lyshark.h头文件,该头文件中包含了我们本篇文章所必须要使用到的结构体定义,这些定义的函数如果不懂请去看LyShark以前的文章,这里就不罗嗦了。
#include <ntifs.h>
#include <ntimage.h>
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
ULONG __Undefined1;
ULONG __Undefined2;
ULONG __Undefined3;
ULONG NonPagedDebugInfo;
ULONG DllBase;
ULONG EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined;
ULONG __Undefined6;
ULONG CheckSum;
ULONG __padding;
ULONG TimeDateStamp;
ULONG __padding;
}KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
typedef struct _RTL_PROCESS_MODULE_INFORMATION
{
HANDLE Section;
PVOID MappedBase;
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT LoadOrderIndex;
USHORT InitOrderIndex;
USHORT LoadCount;
USHORT OffsetToFileName;
UCHAR FullPathName[];
} RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES
{
ULONG NumberOfModules;
RTL_PROCESS_MODULE_INFORMATION Modules[];
} RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;
typedef enum _SYSTEM_INFORMATION_CLASS
{
SystemBasicInformation =x0,
SystemProcessorInformation =x1,
SystemPerformanceInformation =x2,
SystemTimeOfDayInformation =x3,
SystemPathInformation =x4,
SystemProcessInformation =x5,
SystemCallCountInformation =x6,
SystemDeviceInformation =x7,
SystemProcessorPerformanceInformation =x8,
SystemFlagsInformation =x9,
SystemCallTimeInformation =xa,
SystemModuleInformation =xb,
SystemLocksInformation =xc,
SystemStackTraceInformation =xd,
SystemPagedPoolInformation =xe,
SystemNonPagedPoolInformation =xf,
SystemHandleInformation =x10,
SystemObjectInformation =x11,
SystemPageFileInformation =x12,
SystemVdmInstemulInformation =x13,
SystemVdmBopInformation =x14,
SystemFileCacheInformation =x15,
SystemPoolTagInformation =x16,
SystemInterruptInformation =x17,
SystemDpcBehaviorInformation =x18,
SystemFullMemoryInformation =x19,
SystemLoadGdiDriverInformation =x1a,
SystemUnloadGdiDriverInformation =x1b,
SystemTimeAdjustmentInformation =x1c,
SystemSummaryMemoryInformation =x1d,
SystemMirrorMemoryInformation =x1e,
SystemPerformanceTraceInformation =x1f,
SystemObsolete = 0x20,
SystemExceptionInformation =x21,
SystemCrashDumpStateInformation =x22,
SystemKernelDebuggerInformation =x23,
SystemContextSwitchInformation =x24,
SystemRegistryQuotaInformation =x25,
SystemExtendServiceTableInformation =x26,
SystemPrioritySeperation =x27,
SystemVerifierAddDriverInformation =x28,
SystemVerifierRemoveDriverInformation =x29,
SystemProcessorIdleInformation =x2a,
SystemLegacyDriverInformation =x2b,
SystemCurrentTimeZoneInformation =x2c,
SystemLookasideInformation =x2d,
SystemTimeSlipNotification =x2e,
SystemSessionCreate =x2f,
SystemSessionDetach =x30,
SystemSessionInformation =x31,
SystemRangeStartInformation =x32,
SystemVerifierInformation =x33,
SystemVerifierThunkExtend =x34,
SystemSessionProcessInformation =x35,
SystemLoadGdiDriverInSystemSpace =x36,
SystemNumaProcessorMap =x37,
SystemPrefetcherInformation =x38,
SystemExtendedProcessInformation =x39,
SystemRecommendedSharedDataAlignment =x3a,
SystemComPlusPackage =x3b,
SystemNumaAvailableMemory =x3c,
SystemProcessorPowerInformation =x3d,
SystemEmulationBasicInformation =x3e,
SystemEmulationProcessorInformation =x3f,
SystemExtendedHandleInformation =x40,
SystemLostDelayedWriteInformation =x41,
SystemBigPoolInformation =x42,
SystemSessionPoolTagInformation =x43,
SystemSessionMappedViewInformation =x44,
SystemHotpatchInformation =x45,
SystemObjectSecurityMode =x46,
SystemWatchdogTimerHandler =x47,
SystemWatchdogTimerInformation =x48,
SystemLogicalProcessorInformation =x49,
SystemWowSharedInformationObsolete = 0x4a,
SystemRegisterFirmwareTableInformationHandler =x4b,
SystemFirmwareTableInformation =x4c,
SystemModuleInformationEx =x4d,
SystemVerifierTriageInformation =x4e,
SystemSuperfetchInformation =x4f,
SystemMemoryListInformation =x50,
SystemFileCacheInformationEx =x51,
SystemThreadPriorityClientIdInformation =x52,
SystemProcessorIdleCycleTimeInformation =x53,
SystemVerifierCancellationInformation =x54,
SystemProcessorPowerInformationEx =x55,
SystemRefTraceInformation =x56,
SystemSpecialPoolInformation =x57,
SystemProcessIdInformation =x58,
SystemErrorPortInformation =x59,
SystemBootEnvironmentInformation =x5a,
SystemHypervisorInformation =x5b,
SystemVerifierInformationEx =x5c,
SystemTimeZoneInformation =x5d,
SystemImageFileExecutionOptionsInformation =x5e,
SystemCoverageInformation =x5f,
SystemPrefetchPatchInformation =x60,
SystemVerifierFaultsInformation =x61,
SystemSystemPartitionInformation =x62,
SystemSystemDiskInformation =x63,
SystemProcessorPerformanceDistribution =x64,
SystemNumaProximityNodeInformation =x65,
SystemDynamicTimeZoneInformation =x66,
SystemCodeIntegrityInformation =x67,
SystemProcessorMicrocodeUpdateInformation =x68,
SystemProcessorBrandString =x69,
SystemVirtualAddressInformation =x6a,
SystemLogicalProcessorAndGroupInformation =x6b,
SystemProcessorCycleTimeInformation =x6c,
SystemStoreInformation =x6d,
SystemRegistryAppendString =x6e,
SystemAitSamplingValue =x6f,
SystemVhdBootInformation =x70,
SystemCpuQuotaInformation =x71,
SystemNativeBasicInformation =x72,
SystemErrorPortTimeouts =x73,
SystemLowPriorityIoInformation =x74,
SystemBootEntropyInformation =x75,
SystemVerifierCountersInformation =x76,
SystemPagedPoolInformationEx =x77,
SystemSystemPtesInformationEx =x78,
SystemNodeDistanceInformation =x79,
SystemAcpiAuditInformation =x7a,
SystemBasicPerformanceInformation =x7b,
SystemQueryPerformanceCounterInformation =x7c,
SystemSessionBigPoolInformation =x7d,
SystemBootGraphicsInformation =x7e,
SystemScrubPhysicalMemoryInformation =x7f,
SystemBadPageInformation =x80,
SystemProcessorProfileControlArea =x81,
SystemCombinePhysicalMemoryInformation =x82,
SystemEntropyInterruptTimingInformation =x83,
SystemConsoleInformation =x84,
SystemPlatformBinaryInformation =x85,
SystemThrottleNotificationInformation =x86,
SystemHypervisorProcessorCountInformation =x87,
SystemDeviceDataInformation =x88,
SystemDeviceDataEnumerationInformation =x89,
SystemMemoryTopologyInformation =x8a,
SystemMemoryChannelInformation =x8b,
SystemBootLogoInformation =x8c,
SystemProcessorPerformanceInformationEx =x8d,
SystemSpare = 0x8e,
SystemSecureBootPolicyInformation =x8f,
SystemPageFileInformationEx =x90,
SystemSecureBootInformation =x91,
SystemEntropyInterruptTimingRawInformation =x92,
SystemPortableWorkspaceEfiLauncherInformation =x93,
SystemFullProcessInformation =x94,
SystemKernelDebuggerInformationEx =x95,
SystemBootMetadataInformation =x96,
SystemSoftRebootInformation =x97,
SystemElamCertificateInformation =x98,
SystemOfflineDumpConfigInformation =x99,
SystemProcessorFeaturesInformation =x9a,
SystemRegistryReconciliationInformation =x9b,
MaxSystemInfoClass =x9c,
} SYSTEM_INFORMATION_CLASS;
// 声明函数
// By: Lyshark.com
NTSYSAPI PIMAGE_NT_HEADERS NTAPI RtlImageNtHeader(_In_ PVOID Base);
NTSTATUS NTAPI ZwQuerySystemInformation(SYSTEM_INFORMATION_CLASS SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength);
typedef VOID(__cdecl *PMiProcessLoaderEntry)(PKLDR_DATA_TABLE_ENTRY section, IN LOGICAL Insert);
typedef NTSTATUS(*NTQUERYSYSTEMINFORMATION)(IN ULONG SystemInformationClass, OUT PVOID SystemInformation, IN ULONG_PTR SystemInformationLength, OUT PULONG_PTR ReturnLength OPTIONAL);
特征码字符串解析与扫描实现
我们继续,首先实现特征码字符串的解析与扫描实现此处UtilLySharkSearchPattern函数就是LyShark封装过的,这里依次介绍一下参数传递的含义。
- pattern 用于传入一段字符串特征值(以\x开头)
- len 代表输入特征码长度(除去\x后的长度)
- base 代表扫描内存的基地址
- size 代表需要向下扫描的长度
- ppFound 代表扫描到首地址以后返回的内存地址
UtilLySharkSearchPattern如何定位特征
这段代码该如何使用,如下我们以定位IoInitializeTimer为例,演示UtilLySharkSearchPattern如何定位特征的,如下代码pattern变量中就是我们需要定位的特征值,pattern_size则是需要定位的特征码长度,在address地址位置向下扫描128字节,找到则返回到find_address变量内。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include "lyshark.h"
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress =;
UNICODE_STRING uioiTime = { };
RtlInitUnicodeString(&uioiTime, L"IoInitializeTimer");
VariableAddress = (PVOID)MmGetSystemRoutineAddress(&uioiTime);
if (VariableAddress !=)
{
return VariableAddress;
}
return;
}
// 对指定内存执行特征码扫描
NTSTATUS UtilLySharkSearchPattern(IN PUCHAR pattern, IN ULONG_PTR len, IN const VOID* base, IN ULONG_PTR size, OUT PVOID* ppFound)
{
// 计算匹配长度
// LyShark.com 特征码扫描
NT_ASSERT(ppFound != && pattern != 0 && base != 0);
if (ppFound == || pattern == 0 || base == 0)
{
return STATUS_INVALID_PARAMETER;
}
__try
{
for (ULONG_PTR i =; i < size - len; i++)
{
BOOLEAN found = TRUE;
for (ULONG_PTR j =; j < len; j++)
{
if (pattern[j] != ((PUCHAR)base)[i + j])
{
found = FALSE;
break;
}
}
if (found != FALSE)
{
*ppFound = (PUCHAR)base + i;
DbgPrint("[LyShark.com] 特征码匹配地址: %p \n", (PUCHAR)base + i);
return STATUS_SUCCESS;
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return STATUS_UNHANDLED_EXCEPTION;
}
return STATUS_NOT_FOUND;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark.com \n"));
// 返回匹配长度
CHAR pattern[] = "\x\x89\x6c\x24\x10";
PVOID *find_address = NULL;
int pattern_size = sizeof(pattern) -;
DbgPrint("匹配长度: %d \n", pattern_size);
// 得到基地址
PVOID address = GetIoInitializeTimerAddress();
// 扫描特征
NTSTATUS nt = UtilLySharkSearchPattern((PUCHAR)pattern, pattern_size, address,, &find_address);
DbgPrint("[LyShark 返回地址 => ]x%p \n", (ULONG64)find_address);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行驱动程序完成特征定位,并对比定位效果。
RtlImageNtHeader对其PE头部解析
如上述所示定位函数我们已经封装好了,相信你也能感受到这种方式要比使用数组更方便,为了能定位到内核PE结构我们需要使用RtlImageNtHeader来解析,这个内核函数专门用来得到内核程序的PE头部结构的,在下方案例中首先我们使用封装过的LySharkToolsUtilKernelBase函数拿到内核基址,如果你不懂函数实现细节请阅读《驱动开发:内核取ntoskrnl模块基地址》这篇文章,拿到基址以后可以直接使用RtlImageNtHeader对其PE头部进行解析,如下所示。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include "lyshark.h"
// 定义全局变量
static PVOID g_KernelBase =;
static ULONG g_KernelSize =;
// 得到KernelBase基地址
// lyshark.com
PVOID LySharkToolsUtilKernelBase(OUT PULONG pSize)
{
NTSTATUS status = STATUS_SUCCESS;
ULONG bytes =;
PRTL_PROCESS_MODULES pMods =;
PVOID checkPtr =;
UNICODE_STRING routineName;
if (g_KernelBase !=)
{
if (pSize)
{
*pSize = g_KernelSize;
}
return g_KernelBase;
}
RtlInitUnicodeString(&routineName, L"NtOpenFile");
checkPtr = MmGetSystemRoutineAddress(&routineName);
if (checkPtr ==)
return;
__try
{
status = ZwQuerySystemInformation(SystemModuleInformation,, bytes, &bytes);
if (bytes ==)
{
return;
}
pMods = (PRTL_PROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPoolNx, bytes, L"LyShark");
RtlZeroMemory(pMods, bytes);
status = ZwQuerySystemInformation(SystemModuleInformation, pMods, bytes, &bytes);
if (NT_SUCCESS(status))
{
PRTL_PROCESS_MODULE_INFORMATION pMod = pMods->Modules;
for (ULONG i =; i < pMods->NumberOfModules; i++)
{
if (checkPtr >= pMod[i].ImageBase && checkPtr < (PVOID)((PUCHAR)pMod[i].ImageBase + pMod[i].ImageSize))
{
g_KernelBase = pMod[i].ImageBase;
g_KernelSize = pMod[i].ImageSize;
if (pSize)
{
*pSize = g_KernelSize;
}
break;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return;
}
if (pMods)
{
ExFreePoolWithTag(pMods, L"LyShark");
}
DbgPrint("KernelBase = > %p \n", g_KernelBase);
return g_KernelBase;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark.com \n"));
// 获取内核第一个模块的基地址
PVOID base = LySharkToolsUtilKernelBase();
if (!base)
return STATUS_NOT_FOUND;
// 得到NT头部PE+结构
// lyshark.com
PIMAGE_NT_HEADERS pHdr = RtlImageNtHeader(base);
if (!pHdr)
return STATUS_INVALID_IMAGE_FORMAT;
// 首先寻找代码段
PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHdr +);
for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHdr->FileHeader.NumberOfSections; pSection++)
{
ANSI_STRING LySharkSection, LySharkName;
RtlInitAnsiString(&LySharkSection, ".text");
RtlInitAnsiString(&LySharkName, (PCCHAR)pSection->Name);
DbgPrint("[LyShark.PE] 名字: %Z | 地址: %p | 长度: %d \n", LySharkName, (PUCHAR)base + pSection->VirtualAddress, pSection->Misc.VirtualSize);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行这段驱动程序,你会得到当前内核的所有PE节信息,枚举效果如下所示。
既然能够得到PE头部数据了,那么我们只需要扫描这段空间并得到匹配到的数据即可,其实很容易实现,如下代码所示。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include "lyshark.h"
// 定义全局变量
static PVOID g_KernelBase =;
static ULONG g_KernelSize =;
// 得到KernelBase基地址
// lyshark.com
PVOID LySharkToolsUtilKernelBase(OUT PULONG pSize)
{
NTSTATUS status = STATUS_SUCCESS;
ULONG bytes =;
PRTL_PROCESS_MODULES pMods =;
PVOID checkPtr =;
UNICODE_STRING routineName;
if (g_KernelBase !=)
{
if (pSize)
{
*pSize = g_KernelSize;
}
return g_KernelBase;
}
RtlInitUnicodeString(&routineName, L"NtOpenFile");
checkPtr = MmGetSystemRoutineAddress(&routineName);
if (checkPtr ==)
return;
__try
{
status = ZwQuerySystemInformation(SystemModuleInformation,, bytes, &bytes);
if (bytes ==)
{
return;
}
pMods = (PRTL_PROCESS_MODULES)ExAllocatePoolWithTag(NonPagedPoolNx, bytes, L"LyShark");
RtlZeroMemory(pMods, bytes);
status = ZwQuerySystemInformation(SystemModuleInformation, pMods, bytes, &bytes);
if (NT_SUCCESS(status))
{
PRTL_PROCESS_MODULE_INFORMATION pMod = pMods->Modules;
for (ULONG i =; i < pMods->NumberOfModules; i++)
{
if (checkPtr >= pMod[i].ImageBase && checkPtr < (PVOID)((PUCHAR)pMod[i].ImageBase + pMod[i].ImageSize))
{
g_KernelBase = pMod[i].ImageBase;
g_KernelSize = pMod[i].ImageSize;
if (pSize)
{
*pSize = g_KernelSize;
}
break;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return;
}
if (pMods)
{
ExFreePoolWithTag(pMods, L"LyShark");
}
DbgPrint("KernelBase = > %p \n", g_KernelBase);
return g_KernelBase;
}
// 对指定内存执行特征码扫描
NTSTATUS UtilLySharkSearchPattern(IN PUCHAR pattern, IN UCHAR wildcard, IN ULONG_PTR len, IN const VOID* base, IN ULONG_PTR size, OUT PVOID* ppFound)
{
NT_ASSERT(ppFound != && pattern != 0 && base != 0);
if (ppFound == || pattern == 0 || base == 0)
{
return STATUS_INVALID_PARAMETER;
}
__try
{
for (ULONG_PTR i =; i < size - len; i++)
{
BOOLEAN found = TRUE;
for (ULONG_PTR j =; j < len; j++)
{
if (pattern[j] != wildcard && pattern[j] != ((PUCHAR)base)[i + j])
{
found = FALSE;
break;
}
}
if (found != FALSE)
{
*ppFound = (PUCHAR)base + i;
DbgPrint("[LyShark] 特征码匹配地址: %p \n", (PUCHAR)base + i);
return STATUS_SUCCESS;
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return STATUS_UNHANDLED_EXCEPTION;
}
return STATUS_NOT_FOUND;
}
// 扫描代码段中的指令片段
NTSTATUS ByLySharkComUtilScanSection(IN PCCHAR section, IN PUCHAR pattern, IN UCHAR wildcard, IN ULONG_PTR len, OUT PVOID* ppFound)
{
NT_ASSERT(ppFound !=);
if (ppFound ==)
return STATUS_INVALID_PARAMETER;
// 获取内核第一个模块的基地址
PVOID base = LySharkToolsUtilKernelBase();
if (!base)
return STATUS_NOT_FOUND;
// 得到NT头部PE+结构
PIMAGE_NT_HEADERS pHdr = RtlImageNtHeader(base);
if (!pHdr)
return STATUS_INVALID_IMAGE_FORMAT;
// 首先寻找代码段
PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(pHdr +);
for (PIMAGE_SECTION_HEADER pSection = pFirstSection; pSection < pFirstSection + pHdr->FileHeader.NumberOfSections; pSection++)
{
ANSI_STRING LySharkSection, LySharkText;
RtlInitAnsiString(&LySharkSection, section);
RtlInitAnsiString(&LySharkText, (PCCHAR)pSection->Name);
// 判断是不是我们要找的.text节
if (RtlCompareString(&LySharkSection, &LySharkText, TRUE) ==)
{
// 如果是则开始匹配特征码
return UtilLySharkSearchPattern(pattern, wildcard, len, (PUCHAR)base + pSection->VirtualAddress, pSection->Misc.VirtualSize, ppFound);
}
}
return STATUS_NOT_FOUND;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
PMiProcessLoaderEntry m_MiProcessLoaderEntry = NULL;
RTL_OSVERSIONINFOW Version = { };
Version.dwOSVersionInfoSize = sizeof(Version);
RtlGetVersion(&Version);
//获取内核版本号
DbgPrint("主版本: %d -->次版本: %d --> 编译版本: %d", Version.dwMajorVersion, Version.dwMinorVersion, Version.dwBuildNumber);
if (Version.dwMajorVersion ==)
{
// 如果是 win 18363 则匹配特征
if (Version.dwBuildNumber ==)
{
CHAR pattern[] = "\x\x89\x5c\x24\x08";
int pattern_size = sizeof(pattern) -;
ByLySharkComUtilScanSection(".text", (PUCHAR)pattern,xCC, pattern_size, (PVOID *)&m_MiProcessLoaderEntry);
DbgPrint("[LyShark] 输出首地址: %p", m_MiProcessLoaderEntry);
}
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
代码中首先判断系统主版本windows 10 18363如果是则执行匹配,只匹配.text也就是代码段中的数据,当遇到0xcc时则取消继续,否则继续执行枚举,程序输出效果如下所示。
在WinDBG中输入命令!dh 0xfffff8007f600000解析出内核PE头数据,可以看到如下所示,对比无误。