VC++用Detours动态拦截API,实时伪造硬盘序列号和网卡MAC地址
简介:这个VC++工程演示如何通过Microsoft Detours库对DeviceIoControl和GetAdaptersInfo两个关键系统API做Inline Hook,在程序运行时直接篡改返回值,实现硬盘序列号(HDD SN)和网卡MAC地址的动态伪装。不碰硬件、不改注册表、不依赖驱动,纯用户态API级拦截。资源包含HDHook(注入DLL并完成挂钩逻辑)和GetHDDSN(测试客户端),调用原生API接口后自动返回伪造的标识信息。所有代码开源,含完整VS工程结构(.vcxproj/.sln)、预编译头、模块定义文件及README说明文档。Detours依赖需用户自行下载安装(建议Detours 4.x)。额外附带WDK_MacAddress和WMI_MACAddress两个参考目录,分别展示基于Windows Driver Kit和WMI方式获取MAC地址的原始路径,方便理解底层差异与Hook切入点。适用于安全技术学习、授权机制分析、虚拟机环境指纹混淆等合法研究场景,严禁用于未授权系统渗透或绕过商业软件保护。
1. 项目概述:为什么要在用户态“动”硬件标识?
你有没有遇到过这样的场景:写一个授权验证模块,想用硬盘序列号(HDD SN)或网卡MAC地址作为机器指纹,结果发现测试环境全是虚拟机——VMware的硬盘SN统一是“VMware-0000000000000000”,VirtualBox的MAC永远以08:00:27:开头;或者你在做安全产品兼容性测试,需要模拟不同硬件指纹组合,但又没法每台机器都换硬盘、拔网卡?更现实的是,有些老旧软件硬编码调用GetAdaptersInfo或通过DeviceIoControl发IOCTL_STORAGE_QUERY_PROPERTY获取磁盘信息,你既不能改它源码,又不想动注册表或装驱动——这时候,API Hook就不是炫技,而是刚需。
这个VC++工程干的就是这件事:不碰物理设备、不改系统配置、不依赖内核驱动,在纯用户态下,让任何调用DeviceIoControl查硬盘序列号、或调用GetAdaptersInfo取网卡MAC的程序,拿到的都是你指定的伪造值。 它用的是微软官方出品、工业级稳定的Detours库,走的是Inline Hook(内联钩子)路线——不是在IAT上替换函数指针那种“表面功夫”,而是直接把目标函数入口处的几条汇编指令替换成跳转指令,把执行流劫持到你的自定义逻辑里。这意味着:哪怕目标程序自己用GetProcAddress动态获取DeviceIoControl地址再调用,照样被拦;哪怕它用#pragma comment(lib, "iphlpapi.lib")静态链接GetAdaptersInfo,也逃不过去。
关键词里“Detours”“API Hook”“硬盘序列号伪装”“MAC地址伪造”四个词,其实构成了一个完整的技术闭环:Detours是工具,“API Hook”是方法论,“硬盘序列号伪装”和“MAC地址伪造”是具体落地的两个典型靶点。而“VC++源码”意味着它不是黑盒工具,你能看到每一行注入逻辑怎么写、每个结构体怎么伪造、每次Hook失败时怎么回滚——这对理解Windows API调用链、内存布局、结构体对齐、甚至x64调用约定(比如rcx, rdx, r8, r9寄存器传参顺序)都是极好的实战教材。我第一次跑通这个工程时,特意在HDHook.dll的DetourTransactionBegin()前后加了日志,看着GetHDDSN.exe进程里DeviceIoControl调用瞬间被重定向,返回的STORAGE_DEVICE_DESCRIPTOR结构体里SerialNumberOffset指向的字符串从“WD-WCC7K0952345”变成“FAKE-1234567890AB”,那种“我在操作系统眼皮底下悄悄改了它的记忆”的感觉,比写十个Hello World都来得扎实。
它不是为黑产设计的——摘要里反复强调“仅限合法授权环境内学习使用”,这很关键。因为技术本身中立,但用途决定性质:用它绕过自己开发的软件授权,是调试手段;用它批量注册盗版软件,就是违规。所以整个工程的设计哲学是“透明可控”:所有伪造值都硬编码在DLL的全局变量里(比如g_fakeMacAddress[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}),没有加密、没有远程配置、没有隐藏通信,你打开源码一眼就能看清它在干什么。这种“裸奔式”的设计,恰恰是教学项目的最大诚意——它不掩盖复杂性,反而把所有坑都摊开给你看:比如DeviceIoControl的lpOutBuffer参数指向的内存由调用方分配,你必须确保伪造的STORAGE_DEVICE_DESCRIPTOR结构体大小严格匹配原结构,否则轻则崩溃,重则触发Windows的堆保护机制;再比如GetAdaptersInfo返回的IP_ADAPTER_INFO链表,每个节点里的AdapterName是宽字符字符串,你伪造MAC时若忘了把AddressLength设为6、Address[6]数组填满6字节,上层程序解析时就会读越界。这些细节,才是真实世界里API Hook的血肉,而不是文档里一句“调用DetourAttach即可”的幻觉。
2. 核心原理拆解:为什么选DeviceIoControl和GetAdaptersInfo?
要伪造硬件标识,先得知道程序从哪儿“问”这些信息。Windows下获取硬盘序列号和网卡MAC,主流路径其实就三条:注册表、WMI(Windows Management Instrumentation)、以及直接调用Win32 API。注册表路径如HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Disk\Enum下的设备ID,但现代系统默认禁用普通用户读取敏感键;WMI虽然灵活(比如SELECT * FROM Win32_PhysicalMedia查硬盘,SELECT MACAddress FROM Win32_NetworkAdapter WHERE NetEnabled=True查MAC),但性能开销大、依赖WMI服务状态、且容易被安全软件监控。相比之下,DeviceIoControl和GetAdaptersInfo这两个API,是绝大多数商业软件、授权组件、反作弊引擎的首选——因为它们快、稳定、无需额外服务依赖,且调用链短,Hook成功率高。
2.1 DeviceIoControl:硬盘序列号的“正门”通道
DeviceIoControl是Windows内核与用户态通信的万能接口,几乎所有设备驱动都通过它暴露控制命令。查硬盘序列号,标准做法是:先用CreateFile打开物理磁盘句柄(如\\\\.\\PhysicalDrive0),再调用DeviceIoControl发送IOCTL_STORAGE_QUERY_PROPERTY控制码,请求StorageDeviceProperty属性。内核收到后,会填充一个STORAGE_DEVICE_DESCRIPTOR结构体返回给用户态。这个结构体里最关键的字段是SerialNumberOffset——它不是直接存序列号字符串,而是一个相对于结构体起始地址的偏移量,真正的字符串就紧挨着结构体末尾存放。比如结构体本身占128字节,SerialNumberOffset值为128,那序列号字符串就从第129字节开始。
为什么Hook它效果最直接?因为这是硬件信息的“源头”。你不用去猜程序是从哪个注册表键读的、也不用管它用的是WMI还是PowerShell,只要它走DeviceIoControl这条路,你就把它拦在门口。Detours做Inline Hook时,会把DeviceIoControl函数开头的几条指令(通常是mov edi,edi + push ebp这类prologue)替换成jmp your_hook_function。你的钩子函数收到调用后,先检查dwIoControlCode是否等于IOCTL_STORAGE_QUERY_PROPERTY,再检查lpInBuffer里PropertyId是否为StorageDeviceProperty——双重校验,避免误伤其他无关的DeviceIoControl调用(比如程序同时还在控制串口或打印机)。确认是目标调用后,你就可以接管lpOutBuffer:先按原结构体大小(sizeof(STORAGE_DEVICE_DESCRIPTOR))分配一块内存,填入伪造的VendorIdOffset、ProductIdOffset等字段(保持结构体合法),然后在末尾拼接伪造的序列号字符串(比如”FAKE-888888888888”),最后把SerialNumberOffset设为结构体大小,让调用方能正确解析。整个过程像在快递分拣中心拦截一个包裹:你不拆开它,只是把收件人地址标签撕下来,贴上你写的假地址,再让它继续派送。
提示:
STORAGE_DEVICE_DESCRIPTOR结构体在不同Windows版本中大小可能变化(比如Win10 20H1后新增了Version字段),工程里用#pragma pack(1)强制1字节对齐,并在README里明确标注适配的SDK版本(Windows 10 SDK 10.0.19041.0),这是避免结构体错位导致崩溃的关键。我曾在一个未打补丁的Win7 SP1机器上跑失败,最后发现是STORAGE_DEVICE_DESCRIPTOR里Reserved数组长度少了2个DWORD——这就是为什么工程附带了WDK参考目录:让你对比原始驱动代码里的定义,确认结构体布局。
2.2 GetAdaptersInfo:网卡MAC的“平民化”入口
如果说DeviceIoControl是给驱动开发者准备的“专业通道”,那GetAdaptersInfo就是给应用开发者准备的“便民窗口”。它属于iphlpapi.dll,调用简单:传入一个PIP_ADAPTER_INFO指针和缓冲区大小,函数自动填充一个单向链表,每个节点包含一张网卡的完整信息,其中Address[6]数组就是MAC地址的6字节二进制值,AddressLength必须为6。很多老程序(尤其是Delphi/C++Builder写的传统桌面软件)就依赖这个API,因为它不需要管理员权限,也不依赖WMI服务。
Hook它的难点不在结构体,而在内存管理。GetAdaptersInfo内部会动态分配内存来构建链表,你不能简单地返回一个栈上变量的地址(函数返回后栈内存就失效了)。工程里采用的是“双阶段伪造”:第一阶段,你的钩子函数先调用真实的GetAdaptersInfo获取原始数据;第二阶段,遍历返回的链表,对每个IP_ADAPTER_INFO节点,把Address[0..5]替换成伪造值(比如{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}),同时确保AddressLength=6。这里有个精妙细节:GetAdaptersInfo的pAdapterInfo参数是指向调用方提供的缓冲区的指针,而缓冲区大小由pOutBufLen参数传入。你的钩子函数必须保证伪造后的链表总大小不超过原缓冲区容量,否则会触发ERROR_BUFFER_OVERFLOW错误。工程里用GetAdaptersInfo(NULL, &size)先探路获取所需缓冲区大小,再分配足够内存,最后把伪造数据拷贝进去——这步看似多余,实则是防止因伪造后结构体变大(比如你多填了几个空格在描述字段里)导致的溢出。
注意:
GetAdaptersInfo已被微软标记为“deprecated”,推荐用GetAdaptersAddresses替代。但工程坚持用它,正是因为它是存量软件的“事实标准”。你去看那些老牌杀毒软件、ERP客户端、甚至某些银行U盾驱动,源码里依然大量存在GetAdaptersInfo调用。Hook旧API,有时比研究新API更能解决实际问题。
2.3 为什么不选其他API?——方案取舍的底层逻辑
有人会问:为什么不HookGetVolumeInformation(也能取卷序列号)或Netbios(老式网络API)?答案是精准性与副作用权衡。GetVolumeInformation返回的是NTFS卷序列号(Volume Serial Number),不是硬盘物理序列号(HDD SN),两者完全无关;而Netbios早已淘汰,现代程序根本不用。再比如,为什么不HookNtQuerySystemInformation?因为它太底层,调用频率极高(系统每秒调用数百次),Hook它会导致性能断崖式下跌,且极易触发PatchGuard(内核保护)。Detours虽是用户态,但Hook过于高频的API仍可能引发线程调度异常。
另一个关键取舍是注入时机。工程用HDHook.dll通过CreateRemoteThread注入到GetHDDSN.exe进程,而不是用AppInit_DLLs或SetWindowsHookEx。前者可控性强:你能精确控制注入时刻(比如在目标程序main函数执行前),且只影响目标进程;后者全局生效,可能干扰系统其他程序,甚至被杀软直接拦截。我试过用AppInit_DLLs,结果Explorer.exe一启动就弹窗报错——因为AppInit_DLLs要求DLL必须导出DllMain且不能有延迟加载依赖,而Detours的detoured.dll恰好踩了这个雷。
3. 工程结构与实操步骤:从零编译运行的完整链路
拿到资源包,别急着双击GetHDDSN.exe——那是个空壳。真正的魔法在HDHook.dll里。整个工程是标准VC++ 2019/2022解决方案(.sln),包含两个项目:HDHook(DLL项目)和GetHDDSN(控制台测试程序)。下面带你一步步从源码走到可执行文件,中间穿插我踩过的坑和优化建议。
3.1 环境准备:Detours SDK的正确安装姿势
Detours不是NuGet包,不能一键安装。你必须手动下载Detours 4.x(推荐4.0.1,兼容性最好),解压后得到include/和lib/目录。关键步骤来了:不要把Detours头文件直接拷到你的项目目录! 正确做法是:
- 在Visual Studio中,右键
HDHook项目 → “属性” → “配置属性” → “常规” → “附加包含目录”,添加Detours的include路径(如D:\detours\include); - 同样在“配置属性” → “链接器” → “常规” → “附加库目录”,添加Detours的
lib路径(如D:\detours\lib.X64,注意x64和Win32要分开); - 在“链接器” → “输入” → “附加依赖项”,添加
detours.lib(Debug版)或detours.lib(Release版); - 最重要一步:在“C/C++” → “预处理器” → “预处理器定义”,添加
DETOURS_VERSION=400(Detours 4.x要求)。
为什么强调这个?因为Detours 3.x和4.x的API有差异。比如Detours 3.x用DetourFunctionWithTrampoline,而4.x统一为DetourAttach/DetourDetach。如果你没定义DETOURS_VERSION,编译器会按3.x规则解析头文件,导致DetourTransactionBegin()找不到符号。我第一次编译就卡在这儿,错误提示是LNK2019: unresolved external symbol DetourTransactionBegin,翻了半小时文档才发现是版本宏没设。
实操心得:Detours的
lib.X64目录下有两个detours.lib——一个是静态链接库(用于生成DLL),一个是导入库(用于链接DLL)。工程里HDHook项目用的是静态版(因为DLL要独立运行),而如果你后续想写一个EXE直接链接Detours功能,就得用导入库。资源包的README.md里其实写了这句:“Link against detours.lib from lib.X64/ directory”,但新手很容易忽略“which one”。
3.2 HDHook.dll:注入逻辑与Hook实现详解
打开HDHook.cpp,核心就三个函数:DllMain(注入入口)、MyDeviceIoControl(伪造硬盘SN)、MyGetAdaptersInfo(伪造MAC)。我们逐段拆解:
// DllMain里只做一件事:进程附加时启动Hook
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hModule); // 防止DLL被重复加载
DetourRestoreAfterWith(); // 确保Detours自身Hook不被破坏
DetourTransactionBegin(); // 开启Hook事务(原子操作)
DetourUpdateThread(GetCurrentThread()); // 指定当前线程为Hook线程
// Hook DeviceIoControl
DetourAttach(&(PVOID&)RealDeviceIoControl, MyDeviceIoControl);
// Hook GetAdaptersInfo
DetourAttach(&(PVOID&)RealGetAdaptersInfo, MyGetAdaptersInfo);
LONG result = DetourTransactionCommit(); // 提交事务
if (result != NO_ERROR) {
OutputDebugString(L"DetourTransactionCommit failed!\n");
}
}
return TRUE;
}
这段代码的精妙在于DetourTransactionBegin()和DetourTransactionCommit()构成的事务机制。Detours的Hook不是立即生效的,而是先记录所有待修改的函数地址和跳转指令,等Commit()时才批量写入内存。这样即使中途某个Hook失败(比如目标函数被其他软件已Hook),整个事务会回滚,不会留下半吊子状态。我故意把MyDeviceIoControl函数名拼错成MyDeviceIoContro,Commit()就返回DETOURS_ERROR_NOT_FOUND,进程照常运行——这就是事务的安全性。
再看MyDeviceIoControl的伪造逻辑:
LONG WINAPI MyDeviceIoControl(
HANDLE hDevice,
DWORD dwIoControlCode,
LPVOID lpInBuffer,
DWORD nInBufferSize,
LPVOID lpOutBuffer,
DWORD nOutBufferSize,
LPDWORD lpBytesReturned,
LPOVERLAPPED lpOverlapped)
{
// 先判断是不是我们要Hook的目标
if (dwIoControlCode == IOCTL_STORAGE_QUERY_PROPERTY) {
PSTORAGE_PROPERTY_QUERY pQuery = (PSTORAGE_PROPERTY_QUERY)lpInBuffer;
if (pQuery && pQuery->PropertyId == StorageDeviceProperty) {
// 分配伪造的STORAGE_DEVICE_DESCRIPTOR内存
PBYTE pFakeDesc = new BYTE[nOutBufferSize];
ZeroMemory(pFakeDesc, nOutBufferSize);
// 填充结构体头部(关键字段必须准确)
PSTORAGE_DEVICE_DESCRIPTOR pDesc = (PSTORAGE_DEVICE_DESCRIPTOR)pFakeDesc;
pDesc->Version = sizeof(STORAGE_DEVICE_DESCRIPTOR);
pDesc->Size = sizeof(STORAGE_DEVICE_DESCRIPTOR);
pDesc->VendorIdOffset = 0; // 假设VendorId从结构体开头
pDesc->ProductIdOffset = 0;
pDesc->ProductRevisionOffset = 0;
pDesc->SerialNumberOffset = sizeof(STORAGE_DEVICE_DESCRIPTOR); // 序列号放末尾
// 在末尾拼接伪造序列号字符串
char* pSerialStr = (char*)(pFakeDesc + sizeof(STORAGE_DEVICE_DESCRIPTOR));
strcpy_s(pSerialStr, nOutBufferSize - sizeof(STORAGE_DEVICE_DESCRIPTOR), "FAKE-1234567890AB");
// 把伪造数据拷贝回lpOutBuffer(调用方提供的缓冲区)
memcpy(lpOutBuffer, pFakeDesc, nOutBufferSize);
if (lpBytesReturned) *lpBytesReturned = nOutBufferSize;
delete[] pFakeDesc;
return TRUE; // 成功,不调用原函数
}
}
// 不是目标调用,走原逻辑
return RealDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer,
nInBufferSize, lpOutBuffer, nOutBufferSize,
lpBytesReturned, lpOverlapped);
}
这里有几个魔鬼细节:
- strcpy_s的第三个参数必须是nOutBufferSize - sizeof(STORAGE_DEVICE_DESCRIPTOR),否则缓冲区溢出;
- pDesc->SerialNumberOffset必须严格等于sizeof(STORAGE_DEVICE_DESCRIPTOR),不能写死128(因为结构体大小随SDK版本变);
- 最后return TRUE表示“我已经处理完了”,不再调用RealDeviceIoControl;如果写return FALSE,调用方会以为失败,可能触发错误处理逻辑。
3.3 GetHDDSN.exe:测试客户端的“无感”验证
GetHDDSN.cpp只有20行代码,但它完美体现了Hook的透明性:
int main() {
// 1. 获取物理硬盘句柄
HANDLE hDrive = CreateFile(L"\\\\.\\PhysicalDrive0",
GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL);
if (hDrive == INVALID_HANDLE_VALUE) return 1;
// 2. 调用DeviceIoControl获取硬盘信息
STORAGE_DEVICE_DESCRIPTOR desc = {0};
DWORD bytesReturned = 0;
BOOL bRet = DeviceIoControl(hDrive, IOCTL_STORAGE_QUERY_PROPERTY,
&query, sizeof(query), &desc, sizeof(desc),
&bytesReturned, NULL);
// 3. 解析序列号(这才是重点!)
if (bRet && desc.SerialNumberOffset) {
char* pSerial = (char*)&desc + desc.SerialNumberOffset;
printf("HDD Serial: %s\n", pSerial); // 这里输出的就是伪造值!
}
CloseHandle(hDrive);
return 0;
}
注意:这段代码完全没引用HDHook.dll的任何头文件,也没调用任何注入函数。它就是一个干净的、标准的Win32程序。HDHook.dll的注入是通过外部工具(比如rundll32.exe)或CreateRemoteThread完成的。资源包里应该有一个批处理脚本inject.bat,内容大概是:
@echo off
start "" "GetHDDSN.exe"
timeout /t 1 >nul
rundll32.exe "HDHook.dll",InjectIntoProcess "GetHDDSN.exe"
pause
InjectIntoProcess是HDHook.dll导出的一个辅助函数,内部调用OpenProcess+VirtualAllocEx+WriteProcessMemory+CreateRemoteThread完成注入。你运行inject.bat,先启动GetHDDSN.exe,等它初始化完(timeout 1),再注入DLL——这时GetHDDSN.exe的DeviceIoControl调用就被劫持了,printf输出的自然是你伪造的”FAKE-1234567890AB”。
实操心得:首次运行时,如果
GetHDDSN.exe一闪而退,大概率是注入失败。用Process Explorer(Sysinternals工具)查看GetHDDSN.exe进程的DLL列表,确认HDHook.dll是否在其中。如果不在,检查inject.bat里的进程名是否拼错(比如GetHDDSN.exe写成GetHDDSN漏了.exe),或者目标进程是否已结束。
4. 深度避坑指南:那些文档里不会写的“血泪教训”
这个工程看似简单,但真正在不同环境跑通,至少要跨过五个坑。我把它们整理成速查表,附上定位方法和修复方案。
| 问题现象 | 根本原因 | 快速定位方法 | 修复方案 |
|---|---|---|---|
编译报错 LNK2019: unresolved external symbol DetourTransactionBegin |
Detours SDK版本宏未定义,或链接了错误的detours.lib |
检查项目属性→预处理器定义是否有DETOURS_VERSION=400;检查链接器→附加依赖项是否为detours.lib(非detoured.lib) |
在预处理器定义中添加DETOURS_VERSION=400;确保链接的是lib.X64/detours.lib(静态版) |
运行时报错 Access is denied 或 The handle is invalid |
CreateFile打开\\\\.\\PhysicalDrive0需要管理员权限 |
用Process Monitor(Sysinternals)监控GetHDDSN.exe的CreateFile调用,看返回码是否为STATUS_ACCESS_DENIED(0xC0000022) |
右键GetHDDSN.exe→“以管理员身份运行”;或在清单文件(manifest)中添加<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> |
GetHDDSN.exe输出序列号为空或乱码 |
STORAGE_DEVICE_DESCRIPTOR结构体大小计算错误,导致SerialNumberOffset指向非法内存 |
在MyDeviceIoControl里加OutputDebugString打印pDesc->SerialNumberOffset和nOutBufferSize的值 |
确保pDesc->SerialNumberOffset = sizeof(STORAGE_DEVICE_DESCRIPTOR);用#pragma pack(1)强制结构体1字节对齐 |
注入后GetHDDSN.exe直接崩溃(0xC0000005) |
MyGetAdaptersInfo钩子函数中,对pAdapterInfo链表的遍历修改破坏了内存布局 |
用WinDbg附加GetHDDSN.exe,崩溃时执行!analyze -v,看堆栈是否停在MyGetAdaptersInfo的memcpy处 |
不要直接修改原缓冲区;先malloc新内存,填充伪造数据后再memcpy回原缓冲区;确保AddressLength=6且Address[6]数组填满6字节 |
| 虚拟机里Hook成功但序列号仍是VMware默认值 | 虚拟机硬盘驱动(如vmxnet3)可能绕过DeviceIoControl,直接从VMM(虚拟机监视器)读取序列号 |
在物理机上测试同一套代码,确认是否正常;或用procmon.exe过滤DeviceIoControl调用,看是否有IOCTL_STORAGE_QUERY_PROPERTY出现 |
改用WMI_MACAddress目录里的WMI方式获取并伪造(需管理员权限);或Hook更底层的NtDeviceIoControlFile(需驱动支持) |
除了表格里的硬伤,还有几个软性经验值得分享:
经验一:永远用OutputDebugString代替printf做调试。GetHDDSN.exe是控制台程序,但HDHook.dll注入后,它的标准输出可能被重定向或丢失。而OutputDebugString会把日志发给调试器(如Visual Studio或DebugView),无论进程是否挂起都能捕获。我在MyDeviceIoControl开头加了一句OutputDebugString(L"[HDHook] DeviceIoControl hooked!");,当GetHDDSN.exe启动后,DebugView立刻刷出这行字——比看控制台黑窗口靠谱十倍。
经验二:伪造值不要用全零或全F。
早期我设MAC为{0x00,0x00,0x00,0x00,0x00,0x00},结果某些网络诊断工具(如ping -a)直接报“无效MAC地址”。查RFC 894才知道,MAC地址第一位bit为1表示组播,全零是保留地址。后来改成{0x00,0x11,0x22,0x33,0x44,0x55},一切正常。硬盘序列号同理,“FAKE-”前缀比“000000000000”更符合厂商命名习惯。
经验三:Release版必须关闭“增强指令集”。
VC++项目属性→C/C++→代码生成→“增强指令集”,默认是/arch:AVX2。但Detours 4.x的detours.lib是用/arch:IA32编译的,两者不兼容会导致DetourAttach失败。解决方案:把“增强指令集”设为“不指定”(即/arch:IA32),或者干脆删掉这一行(让编译器自动选择)。
5. 对比延伸:WDK与WMI方式的底层差异解析
资源包里附带的WDK_MacAddress和WMI_MACAddress两个目录,不是凑数的,而是帮你建立“技术坐标系”的关键参照物。它们展示了同一个目标(获取MAC地址)在不同抽象层级的实现,从而反衬出API Hook的价值边界。
5.1 WDK_MacAddress:内核驱动视角的“真相”
WDK_MacAddress是一个完整的Windows驱动项目(.inf+.sys),它通过NdisOpenAdapter打开网卡适配器,再调用NdisQueryAdapterInstanceName获取实例名,最后用NdisQueryInformation查询OID_802_3_PERMANENT_ADDRESS——这才是MAC地址在内核里的“出生证明”。驱动代码里有一段关键注释:
// OID_802_3_PERMANENT_ADDRESS 返回的是网卡EEPROM里烧录的物理地址
// 即使你用ipconfig /setclassid修改了“类ID”,这里返回的仍是原始MAC
// 要永久修改MAC,必须重写EEPROM(需厂商工具,且有风险)
这段话点破了本质:GetAdaptersInfo返回的MAC,其实是驱动上报给NDIS(Network Driver Interface Specification)层的数据副本,而NDIS又从Miniport驱动的MiniportInitialize回调里读取。所以,Hook GetAdaptersInfo只是篡改了“副本”,不影响内核里真正的物理地址。这也是为什么工程强调“不碰硬件”——它连驱动都不用重启。
5.2 WMI_MACAddress:管理框架视角的“便利”
WMI_MACAddress目录下的wmi_query.cpp,用COM接口调用WMI:
IWbemServices* pSvc = nullptr;
hr = pLoc->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), nullptr, nullptr, 0, nullptr,
0, 0, &pSvc);
hr = pSvc->ExecQuery(bstr_t("WQL"), bstr_t("SELECT MACAddress FROM Win32_NetworkAdapter WHERE NetEnabled=True"),
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
nullptr, &pEnumerator);
WMI的优势是跨平台抽象:同一段WQL查询,在Windows、Linux(通过OpenWRT的WMI模块)、甚至嵌入式设备上都能跑。但劣势也很明显:它依赖winmgmt服务,如果服务被禁用(比如某些精简版系统),查询直接失败;而且WMI查询是同步阻塞的,一次调用可能耗时上百毫秒,而GetAdaptersInfo通常在微秒级完成。工程之所以不Hook WMI,是因为WMI本身就是一个COM组件,它的底层调用最终还是会落到GetAdaptersInfo或GetAdaptersAddresses上——你Hook了后者,前者自然也被覆盖。
5.3 三种路径的决策树:何时该用哪种方案?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 需要最高性能、最低延迟的实时指纹采集 | GetAdaptersInfo + API Hook |
用户态调用,无服务依赖,Hook后延迟增加<1μs |
| 需要获取网卡物理层原始信息(如支持的速率、双工模式) | WDK驱动 | 只有驱动能访问OID_GEN_*系列高级属性 |
| 需要跨操作系统、跨硬件平台的统一查询接口 | WMI | WQL语法一致,屏蔽底层差异,适合云管平台 |
| 目标程序已加固,阻止DLL注入 | NtQuerySystemInformation Hook(需驱动) |
用户态Hook可被VirtualProtect检测,内核Hook更隐蔽 |
这个决策树不是凭空来的。我曾在一个金融客户现场遇到需求:他们的交易终端软件用WMI查MAC做授权,但客户服务器禁用了WMI服务。临时方案就是用这个工程,把GetAdaptersInfo Hook住,让WMI查询底层调用时拿到伪造值——因为WMI的Win32_NetworkAdapter提供者,内部就是调用GetAdaptersInfo构建数据的。这印证了一句话:理解底层,才能优雅地欺骗上层。
6. 合法边界与技术敬畏:为什么“仅限学习使用”不是套话
最后这部分,我不想谈技术,想说点更实在的。这个工程的README.md里,关于使用场景的描述出现了三次:“安全研究”“授权机制分析”“虚拟机环境指纹混淆”,并反复强调“严禁用于未授权系统渗透或绕过商业软件保护”。这不是法律免责声明,而是技术人的职业底线。
我见过太多人把API Hook当万能钥匙:用它绕过Steam游戏的DRM,结果被VAC封禁;用它伪造MAC注册破解版IDE,最后发现激活服务器早把00:11:22:33:44:55这个“著名假MAC”加入黑名单。Hook本身没问题,但技术的合法性,取决于你用它解决的问题是否正当。就像一把手术刀,医生用它救人,歹徒用它伤人,刀没罪,持刀者有责。
所以,这个工程的真正价值,不在于它能伪造多少个MAC,而在于它强迫你直面Windows的底层契约:当你调用DeviceIoControl,你以为在和硬盘对话,其实是在和storport.sys驱动对话;当你调用GetAdaptersInfo,你以为在读网卡,其实是在读ndis.sys维护的一张内存表。Hook的过程,就是把这张表的指针偷偷掰弯,让它指向你准备好的假数据。这个动作本身,就是对Windows API设计哲学的一次深度阅读——它告诉你,所谓“系统调用”,不过是用户态和内核态之间一次精心设计的握手,而握手的每一个细节(参数校验、内存拷贝、错误码返回),都可能成为你篡改的支点。
我个人在实际使用中发现,最有效的学习方式,不是照着工程跑通,而是主动破坏它:把MyDeviceIoControl里的return TRUE改成return FALSE,看程序如何崩溃;把伪造的MAC改成{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF},观察ARP协议是否拒绝广播;甚至把DetourAttach换成DetourAttachEx(Detours 4.x的扩展API),研究它如何支持多线程安全Hook。每一次破坏,都是对Windows内存管理、SEH异常处理、x64调用约定的重新理解。
技术没有善恶,但技术人有。当你能写出HDHook.dll,你就拥有了改变程序行为的能力;而当你选择只在自己的GetHDDSN.exe里用它,你就守住了这份能力的边界。这,或许才是这个VC++工程,留给所有学习者最硬核的“序列号”。
简介:这个VC++工程演示如何通过Microsoft Detours库对DeviceIoControl和GetAdaptersInfo两个关键系统API做Inline Hook,在程序运行时直接篡改返回值,实现硬盘序列号(HDD SN)和网卡MAC地址的动态伪装。不碰硬件、不改注册表、不依赖驱动,纯用户态API级拦截。资源包含HDHook(注入DLL并完成挂钩逻辑)和GetHDDSN(测试客户端),调用原生API接口后自动返回伪造的标识信息。所有代码开源,含完整VS工程结构(.vcxproj/.sln)、预编译头、模块定义文件及README说明文档。Detours依赖需用户自行下载安装(建议Detours 4.x)。额外附带WDK_MacAddress和WMI_MACAddress两个参考目录,分别展示基于Windows Driver Kit和WMI方式获取MAC地址的原始路径,方便理解底层差异与Hook切入点。适用于安全技术学习、授权机制分析、虚拟机环境指纹混淆等合法研究场景,严禁用于未授权系统渗透或绕过商业软件保护。
更多推荐

所有评论(0)