一、 前言
2014年美国黑帽大会上研究人员JakobLell和Karsten Nohl展示了badusb的攻击方法后,国内与badusb相关的文章虽然有了一些,但是大部分人把相关文章都阅读后还是会有种“不明觉厉”的感觉,badusb仍有一层朦胧的面纱。经过一段时间的学习和研究后,笔者希望通过自己的一些心得体会可以帮助其他人更清晰地认识badusb,也希望这篇文章能够起到一定的启发。这篇文章主要分为五个部分——知识扫盲部分、badusb固件编写部分、badusb配置界面部分、技术展望部分和总结部分。
二、硬件准备
本文使用的硬件是Arduino Leonardo开发板,但是不难将Leonardo开发板替换为其它Arduino开发板。
三、知识扫盲
1. 虽然USB协议有一定的漏洞,但是不是任何USB设备都能制作badusb的。制作badusb有两个核心,一个是能够为USB设备编写相应的固件,另一个是能够将编写的固件烧录到USB设备中。要编写相应的固件就需要掌握USB设备中微控制器(或者说芯片)的指令规范文档(如果Intel不提供开发文档,那么除了Intel自己没有人能为Intel处理器编写程序);而要将固件烧录到USB设备中,要么使用相应的硬件编程器,要么就需要USB设备本身已经存在的bootloader来辅助进行烧录工作(bootloader是USB设备厂商在生产时就放在USB设备中的,网上某些优盘的优盘量产工具也是从厂商流出的,而不是第三方编写的)。要同时满足两个核心条件还是比较难的,即便是JakobLell和Karsten Nohl公布的badusb利用代码也是针对满足一定条件的优盘,因为流出的优盘量产工具有限(既然有优盘量产工具,可以推测相应的优盘中肯定有bootloader或者类似bootloader的固件存在,那么通过逆向优盘量产工具就可以掌握PC端软件和特定优盘的通信方式,进而实现自己的烧录工具)。
2. Arduino 和 teensy这类开发板之所以容易制作badusb,是因为它们采用的微控制器官方有详细的说明文档。最常用的是atmel公司的微控制器,atmel官网提供了各种开发文档和开发相关的库等。
3. Atmel厂商的微控制器整合了SRAM, FLASH和EEPROM。SRAM就好比电脑的内存,不用关心;FLASH高地址存放的是bootloader,低地址则存放用户烧录的固件,芯片加电时直接执行用户的固件,但是芯片复位时会先执行bootloader(这一点是烧录的关键),再执行用户的固件;EEPROM则主要用来存放数据,用户可以随意修改EEPROM中的数据,固件也可以从EEPROM里读取数据(本文的固件示例和PC端程序都利用了这一特性)。
4. Avr libc是一个开源项目,针对atmel厂商的各种微控制器开发C语言库、编译器、烧录工具等一系列辅助工具,还有针对Windows平台的WinAvr项目。Arduino ide的核心其实也是avr libc。
5. Arduino的开发板有相应的bootloader(在FLASH高地址)可以和avr libc项目中的avrdude.exe软件通信,实现固件的烧写和读取动作。在Arduino开发板复位时,会加载bootloader,这时候就可以利用avrdude.exe和bootloader通信。复位操作可以通过开发板上的复位按钮,或者编程实现软复位操作(如果开发板支持的话)。
6. Intel hex 是一种用于编程器的特殊的文件格式,正是因为这种格式,使得我们可以自由控制数据的存储地址。Intel hex的文件格式解析可以自行网上搜索。
四、知识获取途径
1. USB知识获取
如果只是为了简单了解USB设备为什么可以模拟键盘、鼠标等其它设备,网上有许多博客是关于USB规范详解的,也可以简单地看《USB开发大全》和《USB应用开发实例详解》前面关于USB通用协议部分,如果不是对硬件感兴趣没必要深究。
2. Arduino、Avr libc知识获取
这两样其实都是开源的,所以只要有足够的精力和实力,看源代码深入了解相关知识是没问题的。但是如果只是想体验一下制作badusb,可以只看一下Arduino的官方文档和avrdude.exe的相关文档,看这些文档时也没必要深究每个细节,能一定程度上“照葫芦画瓢”就可以了。
五、Badusb固件编写
这里给的固件示例在执行时会从EEPROM中指定的地址读取数据,根据读取的数据和制定的规则发送相应的按键响应给PC主机。
#include"Keyboard.h" #include"EEPROM.h" intmodifier_key = -1; //modifier_key不为-1说明Win键、Ctrl键或者Shift键按下,这个示例特指Win键 intkeycode; //存放要发送的按键代码 unsignedint delay_time = 0; //固件执行下条指令时延迟的时间 unsignedint address = 0; //从EEPROM读取数据的地址,这里要和EEPROM存放数据的地址一致 charnum_char[10] = {0}; //某个数字的字符串形式,这个数字用于delay()的参数,也就是作为睡眠的时间 int num= 0; intindex = 0; //change this to match your platform: voidsetup() { // make pin 2 an input and turn on the // pullup resistor so it goes high unless // connected to ground: pinMode(2, INPUT_PULLUP); Keyboard.begin(); delay(1000); //等待一段时间,让USB设备和PC主机通信完成初始化工作 while((keycode = EEPROM.read(address++))!= 0){//循环从EEPROM中读取数据直到读到数值0(不是数字0),这个值是在向EEPROM中写入数据时在末尾添加的。 if (keycode == '$') { //如果从EEPROM中读到的数值等于'$'的ASCII码 modifier_key = 131; //将modifier_key的值设为Win键的键值,这个值会和后续的按键组合后发送给PC主机 continue; } /* 将两个'&'字符之间的数字作为后续一个按键发送后睡眠的时间,这里之所以要有这个控制值是因为种种因素都会影响按键按下到某个窗口弹出的时间,如果在窗口未弹出就发送了某个按键,这个按键就失效了,后续的按键组合起来也不完整了。比如在按下Win+R键后,还没等运行对话框弹出,固件就发送了"cmd"按键,那么这次启动cmd肯定就失败了。 */ if (keycode == '&') { while ((keycode = EEPROM.read(address++))!= '&') { num_char[index++] = keycode; } sscanf(num_char, "%d",&num); delay_time = num; //将EEPROM中读取的数据转换为数值后保存到delay_time,控制下次发送按键后睡眠时间 memset(num_char, 0, sizeof(num_char));//清空num_char为下次转换做准备 continue; } if (keycode == ';') { //如果从EEPROM中读取的数值等于';'的ASCII码,将发送一个回车键的按键码 keycode = 176; //176等于回车键的按键码 } Write(keycode); //实际发送按键的动作 }; } // voidloop() { // do nothing: while (true); } voidWrite(int code) { if (modifier_key != -1) { //在这个例子中,modifier_key不等于-1就等于Win键的按键码 Keyboard.press(modifier_key); //按下Win键,不放 Keyboard.press(code); //按下另一个按键,不放 Keyboard.releaseAll(); //同时放开Win键和另一个按键,发送Win+某个按键给PC主机 modifier_key = -1; delay(delay_time); //这个时间要么等于0,要么等于两个'&'字符之间指定的值 } else { Keyboard.write(code); //否则直接按下某个按键并放开 delay(delay_time); //这个时间要么等于0,要么等于两个'&'字符之间指定的值 } delay_time = 0; }
这里针对这个固件给几个例子帮助理解:
1. 从EEPROM中依次读取到’$'、’r'($r)表示badusb会按下Win+R键。
2. 从EEPROM中依次读取到’$'、’r'、’;'($r;)表示badusb会按下Win+R,然后按回车键。
3. 从EEPROM中依次读取到’&’、’5′、’0′、’0′、’&’、’$'、’r'、’;'(&500&$r;)表示badusb会按下Win+R键,然后等待500毫秒(保证运行对话框弹出),再按下回车键。
4. 从EEPROM中依次读取到(&500&$rpowershell&400&;Get-Date;)表示badusb先按下Win+R键,等待500毫秒后输入powershell,按回车键后等待400毫秒,再输入Get-Date,最后按下回车键。
六、Badusb配置界面
所谓的Badusb配置界面,就是avrdude.exe的UI界面精简版,再额外提供了控制Badusb执行的动作的功能(本质就是修改了EEPROM特定地址的数据,因为固件是根据EEPROM中的数据执行动作的)。
图1 PC程序界面
这个程序主要就是avrdude.exe的UI界面,只有executable处和address处是为了自定义Badusb动作设计的。partno的选项和programmer的选项是解析选定的avrdude.conf得到的,所以不选择avrdude.conf的话partno和programmer的下拉框将为空;端口号是通过注册表获取到的。
当点击upload按钮或者dump按钮时,先利用编程方式实现复位操作(后面会解释怎么实现的),在短暂的暂停后使用CreateProcess执行avrdude.exe程序;而如果用户在executable处输入了字符串,PC程序会根据用户输入的字符串和Address处给定的地址生成intel hex格式的文件(还记得前面知识扫盲部分提到的intel hex格式?熟悉了intel hex的格式后,完全可以自己写出生成hex文件的代码,所以这里就不贴出代码了),再调用avrdude.exe把生成的intel hex文件烧录到eeprom。下面给个具体的演示。
图2 烧录固件并指定Badusb执行动作
Executable处输入的字符串指示badusb执行的动作,这里badusb先按下Win+R键,等待500毫秒后输入powershell,按下回车键,等待600毫秒后输入Get-Date然后按下回车键,再输入echoabcdefghijklmnopqrstuvwxyz按下回车键,最后再次输入Get-Date和回车键。至于界面中其它一些零碎的参数笔者怎么知道的后面会说,实在不行完全可以一个一个尝试。
效果演示:
视频没有演示badusb的危害多大(比如从服务器下载木马执行、窃取个人信息等),毕竟这些更多的属于powershell脚本知识、cmd命令之类的,不是这篇文章的目的。
演示视频中主要经历了以下几个阶段:
1. 选择了avrdude.conf文件后partno和programmer下拉框会展示avrdude.exe支持的芯片型号和编程器(编程器本质上是和bootloader通信的协议)。
2. 插上开发板(有提示音)后,port下拉框会展示开发板的串口号。
3. 在executable编辑框中输入badusb需要执行的按键序列”&500&$rcmd”,再选择要烧录到FLASH的固件(只需要烧录一次固件,以后都不需要了),点击upload后两次弹出命令窗口进行烧录(第一次烧录固件,第二次向EEPROM中写入数据),发现开发板重启后弹出了运行对话框并输入了”cmd”。
4. 修改executable编辑框的内容为”&500&$rcmd;”(多了一个分号,也就是多按了一个回车键),再次点击upload(因为没有选择固件,所以不会进行固件的烧录,只会修改EEPROM的数据),发现开发板重启后弹出运行对话框紧接着迅速弹出了命令窗口。
5. 之后又修改了两次executable编辑框的内容并烧录以修改EEPROM的数据,发现一次是只弹出了powershell窗口,一次是弹出了powershell窗口后又执行了三条powershell指令。
七、技术展望
这次的示例只是展示了利用arduino leonardo开发板模拟usb键盘,而windows一旦锁屏,模拟成键盘鼠标之类的badusb就失效了。而有人发现在锁屏状态下插入网卡会让windows操作系统发送dhcp请求给新插入的网卡分配ip,那么是不是可以让usb设备模拟成网卡兼dhcp服务器兼dns服务器,达到在锁屏状态下通过badusb劫持流量、窃取信息的目的?虽然有局限性,但是也说明了一个问题——badusb不只是简单地利用usb协议的漏洞,还可以结合操作系统的一系列特性(缺陷?)在看似不可能的环境下执行,badusb的技术还有很多可以探索的地方。
八、总结
1. 理一下思路,要制作badusb,就要保证能编写出相应的固件并能通过某种手段将固件烧录到usb设备中。要想通过这两点,最方便的就是使用arduino或者teensy这类开发板,否则就需要看有没有官方的集成开发环境,比如Cypress官网就提供了详细的开发文档、示例、开发环境等。
2. arduino开发板主要使用的是atmel厂商的微控制器,编译工具和烧录工具也是来自开源项目avr libc。可以在arduino ide中“文件->首选项->设置”勾上显示编译和上传的详细输出,然后使用ide的上传功能上传一个示例代码,在输出窗口就能看到很多有用的信息。附上一个示例:
图3 arduino ide详细输出信息(a)
3. 看到图4的输出信息,应该可以联想到通过软件复位开发板其实是通过以1200bps速率和开发板进行串口通信,再进一步去看Windows串口通信时发现还需要设置几个其他参数,这时候怎么办呢?只能看arduinoide是怎么设置那些参数的,通过以下几个步骤寻找到arduino ide设置相关参数的源代码(arduino ide的源代码github上有):
①在源代码根目录使用findstr /Sn /c:”Foundupload port”查找含有字符串“Found upload port”的文件,发现路径为arduino-core\src\cc\arduino\packages\uploaders\SerialUploader.java:276:(findstr是Windows提供的在文档中查找字符串的工具)
②查看Serial Uploader代码,发现字符串“Found upload port”在waitForUploadPort方法中,进一步发现调用了waitForUploadPort方法的是uploadUsingPreferences方法,在uploadUsingPreferences方法中发现调用了Serial.touchForCDCReset方法,通过方法名字猜测这个函数和开发板复位有关。
③再次使用findstr /Sn /c:”touchForCDCReset”找到touchForCDCReset方法的实现是在文件arduino-core\src\processing\app\Serial.java:90:中。
④在Serial.java中发现有这么一行代码:
serialPort.setParams(1200,8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
到这里可以大胆猜测出利用串口通信的方式复位开发板时除了1200这个参数值外其他参数的值了。
图4 arduino ide详细输出信息(b)
4. 通过图5可以看到arduino ide在调用avrdude.exe烧录固件到我的开发板时的一系列参数(-p就是partno,-c就是programmer,-b就是baudrate,可以发现和我前面演示时设定的参数是一样的,因为我的那些参数就是根据这里显示的设置的)。有兴趣的可以看看“Arduino安装路径\hardware\arduino\avr”目录下的boards.txt、platform.txt和programmers.txt,应该会有很多收获。
5. 想利用arduino开发板制作有用的,或者扩展性强的badusb,需要理清以下逻辑:
①arduino开发板使用的主要是atmel厂商的微控制器,而atmel厂商的微控制器集成了SRAM,FlASH和EEPROM,其中arduino开发板中的FLASH高地址存放了某种bootloader,低地址则存放用户上传到开发板的固件;arduino开发板的EERPOM可以根据自己的情况使用。
②arduino开发板复位时,先启动bootloader,一段时间(通常是几秒)后启动用户的固件。而avrdude.exe烧录过程其实就是在复位时和arduino开发板的bootloader通信,所以只有在bootloader启动阶段利用avrdude.exe才能成功烧录。一定要把握好时间。
③固件程序可以从eeprom或flash中读取数据,而intelhex格式文件可以控制将数据写入特定存储器的特定地址,利用这一点可以极大的扩展badusb的功能,而且将要执行的指令放在flash或者eeprom中也能在一定程度上起到隐藏效果。
Arduino+Avr libc制作badusb效果展示: