ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VC++实现禁用USB接口与禁止上网:注册表/WMI/防火墙实战解析

VC++实现禁用USB接口与禁止上网:注册表/WMI/防火墙实战解析 简介面向系统管理与网络安全方向开发者的VC实战资源讲解如何调用Windows设备管理与网络接口API实现禁用USB接口和禁止上网两大管控功能。程序涉及SetupDiGetClassDevs、CM_Disable_DevNode、GetAdaptersInfo、SetInterfaceState等关键函数覆盖设备枚举、状态设置、权限处理等完整流程适合有一定C/C基础、希望掌握系统级硬件与网络控制的读者。资源包共49个文件主要为21个头文件和18个CPP源文件配套工程配置.dsp/.dsw、界面与图标资源.rc/.rc2/.ico/.bmp以及编译好的可执行示例大小仅294KB。源码将USB设备禁用、网络接口禁用、界面展示等功能模块化封装结构清晰便于直接阅读和二次开发。已有1718人学习对理解Windows底层API调用和系统功能定制很有参考价值。1. 用VC做禁用USB接口和禁止上网功能的出发点用VC写一个工具同时控制禁用USB接口和禁止上网功能是局域网终端管理里很常见的诉求。制造车间的工控机不能随便插入U盘拷贝程序又要断开外网防止代码外泄企业财务机临时要断网但远程桌面的端口还想保留。修改组策略需要域环境在非域或Windows 10家庭版上经常不生效。更直接的办法是自己写一个带管理员权限的VC程序通过注册表、WMI、Windows防火墙API把系统和网络状态切过去。这篇文章不是讲内核驱动或WFP过滤而是讲一线运维能落地、能回滚、能排错的Win32层方案完整覆盖“怎么做、参数怎么设、坑在哪”。2. 禁用USB接口VC里绕不开的注册表和服务控制2.1 三个控制层服务Start值、设备安装策略、设备实例要禁用USB接口不是在User32里发一条广播就能完成的。Windows枚举USB设备时主要看三处USB主控制器驱动、Hub驱动、以及大容量存储类驱动USBSTOR。常见的管理工具大多是三层组合先把USBSTOR驱动的启动类型改成禁用再用DeviceInstall设备安装策略拒绝新设备最后清理已经枚举出来的设备残留。第一层是服务控制。注册表路径在HKLM\SYSTEM\CurrentControlSet\Services\USBSTOR里面有个Start值控制驱动是否随系统启动。改成4表示禁用0是boot1是system2是auto3是demand。把Start改成4新插入的U盘不会加载驱动也不分配盘符。但已经识别过的设备在USBSTOR\Enum子键里还会有残留直接插上去可能被系统从缓存中重新枚举所以还需要第二层。第二层是设备安装策略。路径在HKLM\SOFTWARE\Policies\Microsoft\Windows\DeviceInstall\Restrictions设置DenyDeviceIDs1并写入要拦截的硬件ID再配合DenyDeviceIDsRetroactive1让已装设备也被踢出。这层相当于在驱动加载前就被PnP管理器拦截比只改USBSTOR更彻底。第三层是设备实例级。用SetupAPI枚举USB设备调用SetupDiCallClassInstaller的DIF_PROPERTYCHANGE把设备disable。这种做法的粒度最细可以只禁用机箱前面板的两个端口但代码量明显大很多。我一般不会直接用VC写这一层除非客户明确要求“禁指定端口”。大多数落地场景前两层已经够了。下面这张表是我做这类需求时的选型依据。方法作用位置是否立即生效恢复难度适用场景USBSTOR Start4驱动服务新设备立即已插需重载低只禁U盘、移动硬盘DeviceInstall Restrictions设备安装策略重启或重启PlugPlay服务中禁某类硬件IDSetupAPI设备实例禁用设备树立即中精细控制某个USB端口2.2 用VC修改USBSTOR的Start值并清理Enum残留下面是一个最小可用的函数开关USB大容量存储驱动。// UsbControl.cpp #include windows.h #include tchar.h #include stdio.h #include shlwapi.h #pragma comment(lib, shlwapi.lib) INT_PTR SetUsbStorageState(BOOL bDisable) { HKEY hKey NULL; DWORD dwStart bDisable ? 4 : 3; // 4禁用 3手动 LPCTSTR lpPath _T(SYSTEM\\CurrentControlSet\\Services\\USBSTOR); LONG lRet RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpPath, 0, KEY_SET_VALUE | KEY_WOW64_64KEY, hKey); if (lRet ! ERROR_SUCCESS) { _tprintf(_T(RegOpenKeyEx failed: %ld\n), lRet); return -1; } lRet RegSetValueEx(hKey, _T(Start), 0, REG_DWORD, (const BYTE*)dwStart, sizeof(dwStart)); RegCloseKey(hKey); if (lRet ! ERROR_SUCCESS) return -2; return 0; } DWORD ClearUsbStorageEnum() { LPCTSTR lpPath _T(SYSTEM\\CurrentControlSet\\Services\\USBSTOR); HKEY hKey NULL; LONG lRet RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpPath, 0, KEY_WRITE | KEY_WOW64_64KEY, hKey); if (lRet ! ERROR_SUCCESS) return lRet; DWORD dwIndex 0; TCHAR szSubKey[128]; DWORD cchName 128; while (RegEnumKeyEx(hKey, dwIndex, szSubKey, cchName, NULL, NULL, NULL, NULL) ERROR_SUCCESS) { if (lstrcmpi(szSubKey, _T(Enum)) 0) { SHDeleteKey(hKey, _T(Enum)); break; } cchName 128; dwIndex; } RegCloseKey(hKey); return ERROR_SUCCESS; }逻辑说明第一步打开USBSTOR服务注册表项写入Start4。第二步枚举该服务子键找到Enum后删除。删除Enum是为了让系统在下一次插入设备时重新走一遍PnP安装流程而不是沿用旧的设备缓存。如果只改Start已经插着的U盘即使图标消失在设备暗黑列表里依然可能被识别。参数说明注册表访问必须带上KEY_WOW64_64KEY因为VC工程如果编译成32位在64位Windows上会被RegOpenKeyEx重定向到WOW6432Node改不到真实的USBSTOR服务。这个坑很隐蔽我见过不少工具写进去之后regedit看值没变就是因为忘了加这个标志。2.3 用DeviceInstall限制策略兜底并写一批要拒绝的硬件ID设备安装策略适合按硬件ID做黑白名单比如只拒绝USB大容量存储不影响USB键鼠。int SetDeviceInstallRestrictions(BOOL bDeny) { HKEY hRoot; DWORD dwDisposition; LPCTSTR lpPath _T(SOFTWARE\\Policies\\Microsoft\\Windows\\DeviceInstall\\Restrictions); LONG lRet RegCreateKeyEx(HKEY_LOCAL_MACHINE, lpPath, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_WOW64_64KEY, NULL, hRoot, dwDisposition); if (lRet ! ERROR_SUCCESS) return -1; DWORD dwDeny bDeny ? 1 : 0; RegSetValueEx(hRoot, _T(DenyDeviceIDs), 0, REG_DWORD, (BYTE*)dwDeny, sizeof(dwDeny)); RegSetValueEx(hRoot, _T(DenyDeviceIDsRetroactive), 0, REG_DWORD, (BYTE*)dwDeny, sizeof(dwDeny)); // 写入拦截列表* 代表所有USB大容量存储设备 if (bDeny) { HKEY hList; RegCreateKeyEx(hRoot, _T(DenyDeviceIDsList), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_WOW64_64KEY, NULL, hList, dwDisposition); RegSetValueEx(hList, _T(1), 0, REG_SZ, (const BYTE*)_T(*USBSTOR*), sizeof(TCHAR) * 11); RegCloseKey(hList); } RegCloseKey(hRoot); return 0; }这段代码把追加到DenyDeviceIDsList里的硬件ID设为*USBSTOR*然后用DenyDeviceIDsRetroactive1让已安装的设备也被拒绝。恢复时把DenyDeviceIDs设回0即可但列表里的值不会自动删建议恢复时把Restrictions键整体删除避免留着半条规则。提示做USB接口禁用后用USBPcap配合Wireshark抓USB总线仍能看到设备枚举的SETUP包。这并不代表禁用失败只能说明物理层还在工作驱动层已经被挡住。要真正断开引脚电平只能靠硬件继电器或者BIOS里的USB控制器开关。3. 禁止上网功能的VC实现从禁网卡到防火墙3.1 选型WMI禁网卡、IP Helper路由、Windows防火墙禁止上网比禁用USB更敏感因为网卡一旦禁用远程管理通道会立刻断掉。所以在设计时就要想清楚是“彻底断网”还是“只断Internet”。如果是怕员工访问外网但内网办公系统还要用那就不能一刀切禁用网卡。我常用的组合是两个层次第一层用WMI禁用物理网卡作为硬切断第二层用Windows防火墙的出站规则作为白名单边界。如果客户要求保留远程桌面则多写一条allow规则放行3389端口。三个方案的对比方案API/接口影响范围回滚手段禁用网络适配器WMI Win32_NetworkAdapter断掉该适配器所有连接Enable方法恢复删除默认路由GetIpForwardTable/DeleteIpForwardEntry断开到Internet的路由CreateIpForwardEntry恢复防火墙出站阻止INetFwPolicy2或netsh按端口/程序/接口过滤删除规则从实际运维角度防火墙规则是最灵活的但也是忘记回滚概率最高的禁用网卡是最容易验证的拔网线就断网但误操作代价也大。通常我让客户选一般都选防火墙因为远程桌面还能留着。3.2 用VC调用WMI禁用指定网络适配器WMI是跨系统版本最稳的方案比直接操作SetupAPI要简单得多。下面这段代码演示如何按连接名称禁用“以太网”适配器。#include windows.h #include comdef.h #include Wbemidl.h #include string #pragma comment(lib, wbemuuid.lib) HRESULT SetAdapterOnline(const wchar_t* wsName, BOOL bEnable) { HRESULT hr CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hr)) return hr; hr CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL); if (FAILED(hr) hr ! RPC_E_TOO_LATE) { CoUninitialize(); return hr; } IWbemLocator *pLoc NULL; IWbemServices *pSvc NULL; hr CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)pLoc); if (FAILED(hr)) { CoUninitialize(); return hr; } hr pLoc-ConnectServer(_bstr_t(LROOT\\CIMV2), NULL, NULL, 0, NULL, 0, 0, pSvc); if (FAILED(hr)) { pLoc-Release(); CoUninitialize(); return hr; } std::wstring wql LSELECT * FROM Win32_NetworkAdapter WHERE NetConnectionID; wql wsName; wql L; IEnumWbemClassObject *pEnum NULL; hr pSvc-ExecQuery(_bstr_t(LWQL), _bstr_t(wql.c_str()), WBEM_FLAG_RETURN_IMMEDIATELY | WBEM_FLAG_FORWARD_ONLY, NULL, pEnum); if (SUCCEEDED(hr) pEnum) { IWbemClassObject *pObj NULL; ULONG uRet 0; while (pEnum-Next(WBEM_INFINITE, 1, pObj, uRet) S_OK uRet 1) { VARIANT vt; VariantInit(vt); pObj-Get(_bstr_t(L__PATH), 0, vt, NULL, NULL); std::wstring wsPath vt.bstrVal; VariantClear(vt); IWbemClassObject *pInClass NULL, *pIn NULL; if (SUCCEEDED(pObj-GetMethod(bEnable ? LEnable : LDisable, 0, NULL, pInClass))) { pInClass-SpawnInstance(0, pIn); pSvc-ExecMethod(_bstr_t(wsPath.c_str()), _bstr_t(bEnable ? LEnable : LDisable), 0, NULL, pIn, NULL, NULL); } if (pIn) pIn-Release(); if (pInClass) pInClass-Release(); pObj-Release(); } pEnum-Release(); } pSvc-Release(); pLoc-Release(); CoUninitialize(); return hr; }逻辑说明通过WQL查询指定名称的网卡实例然后取到WMI对象路径__PATH最后调用Disable/Enable方法。方法无入参所以SpawnInstance出来的对象直接传给ExecMethod即可。参数说明NetConnectionID是控制面板里看到的“以太网”不一定等于适配器名。中文系统可能叫“以太网 2”或者“本地连接”。更稳的做法是先执行SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapterTrue枚举一遍打印出所有NetConnectionID再让用户选。提示调用Disable之后网络会话会立刻断掉如果程序自身依赖网络下载则必须在调用前把后续逻辑缓存起来。我习惯用CreateProcess调用一个延迟5秒的批处理来完成禁用留出脚本刷新的间隔。3.3 用IP Helper API删除默认路由只禁网卡还有一种漏网情况机器上有多个网卡比如有线加4G模块禁掉以太网后4G Dongle会立刻接管。所以还需要删除默认路由。用命令行最简单route delete 0.0.0.0 mask 0.0.0.0在VC里可以直接调用iphlpapi函数避免拉起进程。#include iphlpapi.h #pragma comment(lib, iphlpapi.lib) BOOL ClearDefaultRoute() { DWORD dwSize 0; if (GetIpForwardTable(NULL, dwSize, FALSE) ! ERROR_INSUFFICIENT_BUFFER) return FALSE; PMIB_IPFORWARDTABLE pTable (PMIB_IPFORWARDTABLE)new BYTE[dwSize]; if (GetIpForwardTable(pTable, dwSize, FALSE) ! NO_ERROR) { delete[] pTable; return FALSE; } for (DWORD i 0; i pTable-dwNumEntries; i) { if (pTable-table[i].dwForwardDest 0 pTable-table[i].dwForwardMask 0) { DeleteIpForwardEntry(pTable-table[i]); } } delete[] pTable; return TRUE; }说明这段代码遍历IPv4路由表把所有目标地址和掩码都是0的默认路由删掉。它只影响需要走外网的流量本地局域网通信不受影响。恢复时需要把默认网关加回来最好提前用GetIpForwardTable把原条目保存到INI或注册表里否则恢复时只能靠自动DHCP重新下发。3.4 用netsh命令封装Windows防火墙规则为了保留远程管理推荐先加一条允许远程桌面的规则再加一条阻止所有出站的规则。VC里用CreateProcess执行netsh不直接碰COM代码可读性和可维护性都好很多。void ExecNetsh(LPCTSTR szArgs) { TCHAR szCmd[512] {0}; wsprintf(szCmd, _T(netsh %s), szArgs); STARTUPINFO si { sizeof(si) }; PROCESS_INFORMATION pi; if (CreateProcess(NULL, szCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, si, pi)) { WaitForSingleObject(pi.hProcess, INFINITE); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); } } void ApplyFirewallBlock() { ExecNetsh(_T(advfirewall firewall delete rule name\BlockAllOut\)); ExecNetsh(_T(advfirewall firewall add rule name\BlockAllOut\ dirout actionblock enableyes)); } void AllowRemoteDesktop() { ExecNetsh(_T(advfirewall firewall add rule name\Allow_RDP\ dirout actionallow protocolTCP localport3389 remoteipany enableyes)); }逻辑说明dirout actionblock会阻止所有匹配该规则的外出连接规则优先级低于允许规则所以先建一条allow规则再建block规则允许规则优先放行3389。enableyes确保规则在运行时生效。参数说明如果只想阻止员工访问外网但允许内网可以在add rule里加remoteiplocal subnet把外网IP段排除在外。规则名称用固定值方便后续delete rule name精确删除不会误删用户自建规则。4. 把禁用USB接口和禁止上网功能整合成一个VC管理工具4.1 控制台程序加命令行参数比MFC弹窗更适合运维这个工具的使用者是管理员自己不是普通用户用MFC对话框反而累赘。我建议做成控制台程序定义两个动作restrict执行限制restore恢复。这样能直接拼进CMDB脚本或计划任务。代码入口如下。int wmain(int argc, wchar_t* argv[]) { if (argc 2) { wprintf(LUsage: UsbNetGuard.exe [restrict|restore]\n); return 1; } if (lstrcmpi(argv[1], Lrestrict) 0) return ApplyRestriction(TRUE); else if (lstrcmpi(argv[1], Lrestore) 0) return ApplyRestriction(FALSE); else return 1; }逻辑说明程序根据第一个参数决定限制还是恢复。调用ApplyRestriction这个函数统一执行前面写的USB和网络操作。相比MFC按钮命令行方式更容易被脚本和组策略调用也方便做自动化回归测试。4.2 管理员权限和单实例互斥修改注册表和WMI都需要管理员权限所以进程启动时必须提权。使用IsUserAnAdmin判断配合runas重新拉起自身。#include shellapi.h BOOL IsRunAsAdmin() { return IsUserAnAdmin(); } void ReLaunchAsAdmin(const wchar_t* szCmdLine) { SHELLEXECUTEINFO sei {0}; sei.cbSize sizeof(sei); sei.lpVerb Lrunas; sei.lpFile LUsbNetGuard.exe; sei.lpParameters szCmdLine; sei.nShow SW_HIDE; ShellExecuteEx(sei); }再创建一个全局互斥体防止多个实例同时写注册表产生竞态。int EnsureSingleInstance() { HANDLE hMutex CreateMutex(NULL, TRUE, LGlobal\\UsbNetGuard); if (!hMutex || GetLastError() ERROR_ALREADY_EXISTS) return 0; return 1; }4.3 统一执行顺序先USB后网络执行顺序有讲究。一次常规限制操作我会按这个顺序来设置DeviceInstall设备安装策略拒绝新的USB设备。设置USBSTOR服务为禁用。删除USBSTOR的Enum残留。添加防火墙出站阻止规则。删除默认路由。这里先禁USB再断网是为了防止在网络已经断开的情况下有人把U盘插入后驱动没被及时拦下。驱动安装策略必须最早生效。恢复顺序则反过来先恢复网络再恢复USB。int ApplyRestriction(BOOL bRestrict) { if (bRestrict) { SetDeviceInstallRestrictions(TRUE); SetUsbStorageState(TRUE); ClearUsbStorageEnum(); AllowRemoteDesktop(); ApplyFirewallBlock(); ClearDefaultRoute(); } else { SetDeviceInstallRestrictions(FALSE); SetUsbStorageState(FALSE); ExecNetsh(_T(advfirewall firewall delete rule name\BlockAllOut\)); SetAdapterOnline(L以太网, TRUE); } return 0; }参数说明AllowRemoteDesktop()要先于ApplyFirewallBlock()执行否则阻塞规则生效后后续创建进程都不再访问网络但本机加规则不受影响只是顺序清晰更易于阅读。恢复时没有调用ClearUsbStorageEnum因为USBSTOR驱动重新启用后系统会重新生成枚举信息。4.4 原始状态备份避免恢复时把本已禁用的设备又打开恢复操作最怕的就是把设备原来就是禁用的状态误改成启用。所以在限制前要把USBSTOR的Start值和网络适配器状态记录到注册表或本地文件。这里给一个最简单的记录方式。void SaveOriginalState() { HKEY hKey; RegCreateKeyEx(HKEY_LOCAL_MACHINE, LSOFTWARE\\UsbNetGuard, 0, NULL, 0, KEY_WRITE, NULL, hKey, NULL); DWORD dwUsbStart 3; DWORD dwSize sizeof(dwUsbStart); RegGetValue(HKEY_LOCAL_MACHINE, LSYSTEM\\CurrentControlSet\\Services\\USBSTOR, LStart, RRF_RT_REG_DWORD, NULL, dwUsbStart, dwSize); RegSetValueEx(hKey, LUsbStart, 0, REG_DWORD, (BYTE*)dwUsbStart, sizeof(dwUsbStart)); // TODO: 在这里把当前网卡列表Name写入 RegCloseKey(hKey); }这样恢复时读UsbStart变量的值再写回USBSTOR避免把原本禁用状态的U盘服务打开。网卡的状态备份可以用WMI枚举所有PhysicalAdapter记录其NetConnectionID和NetEnabled属性到文件中恢复时只启用备份时标记为启用过的网卡。5. 验证禁用USB接口和禁止上网功能时容易踩的三个错误5.1 验证USB禁用的三个层次第一层看设备管理器执行限制后插入U盘如果看到“大容量存储设备”带黄色感叹号说明PnP层已经拒绝如果设备正常出现但盘符出不来说明是USBSTOR驱动层被禁。两者都算正确。第二层看是否存在残留盘符把USBSTOR\Enum删除后原盘符不会立刻消失需要重启或手动重启设备管理器。验证时最好记录插入U盘前的盘符列表执行后再对比。第三层看总线抓包用USBPcap抓USB总线如果能看到设备的GET_DESCRIPTOR请求但后续没有BOT/SCSI命令表示物理连接正常是驱动层拒绝。这是正常现象不是失败。5.2 验证禁止上网是否误伤内网最简单的方法在限制前后分别执行ping 内网网关和ping 114.114.114.114。限制后内网网关通、外网超时说明默认路由或防火墙规则生效。如果再执行route print看到目标0.0.0.0的路由消失那是路由删除生效如果路由还在但外网不通则是防火墙规则生效。还有一个常见错误是只删除默认路由但DHCP客户端在几分钟后又会重新申请到默认网关。所以在方案里必须配合防火墙规则出站阻止或者把DHCP客户端的注册表租约缓存清掉。我一般会两套都做单靠路由不可靠。5.3 错误码和恢复口令现象原因处理方式0x5 拒绝访问没有提权或注册表权限不足用runas提权或检查是否SYSTEM账户运行0x540 网络适配器不存在WQL名称与系统NetConnectionID不匹配枚举一次所有网卡名称动态匹配0x57 参数错误WMI方法入参或对象路径错误检查__PATH字符串是否完整USB残留盘符Enum未删除或DenyDeviceIDsRetroactive0删除Enum子键并重启设备管理器防火墙规则不生效未指定profile或组策略覆盖显式加profilesany再检查组策略恢复后上网仍然失败默认路由没恢复执行ipconfig /renew或手动加网关建议在工具里加一个隐藏参数status输出当前关键注册表值和网卡状态方便分发给远程管理员排查。用退避等待策略禁用USB接口后隔2秒再清Enum避免设备枚举正在进行时删除注册表导致蓝屏。本文还有配套的精品资源点击获取
返回列表