背景
最近在打魔兽,玩的服务器需要挂机,需要执行一些自动化的操作。
虽然之前有事件过可以通过windows的一些hook来模拟键盘输入,写一个小脚本来实现这个功能,但是最近官方严打,如果使用传统的方式来hook恐怕被检测到。
如果我使用真实的键盘呢? 应该就没问题了!
我手上正好有个ESP32模块,他有蓝牙和WiFi模块,我可以用他的蓝牙模拟一个蓝牙键盘,再用他的WiFi模块来联网来接受我的指令,我就可以通过编程的方式来自动化的发送键盘指令,再通过蓝牙键盘(模拟)的方式来控制我的wow了。
下面是我调试的一个ESP32的脚步,他会自动连接WiFi,然后会有个蓝牙出来。电脑连接这个蓝牙,然后调用接口:
curl "http://192.168.1.100/a"
# 这样就会发送一个a的按键指令
下面是ESP32代码:
#include <WiFi.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEHIDDevice.h>
#include <HIDTypes.h>
#include <unordered_map>
// Define a mapping from strings to HID keycodes
std::unordered_map<std::string, uint8_t> keyMap = {
{"a", 0x04}, {"b", 0x05}, {"c", 0x06}, {"d", 0x07}, {"e", 0x08},
{"f", 0x09}, {"g", 0x0A}, {"h", 0x0B}, {"i", 0x0C}, {"j", 0x0D},
{"k", 0x0E}, {"l", 0x0F}, {"m", 0x10}, {"n", 0x11}, {"o", 0x12},
{"p", 0x13}, {"q", 0x14}, {"r", 0x15}, {"s", 0x16}, {"t", 0x17},
{"u", 0x18}, {"v", 0x19}, {"w", 0x1A}, {"x", 0x1B}, {"y", 0x1C},
{"z", 0x1D}, {"1", 0x1E}, {"2", 0x1F}, {"3", 0x20}, {"4", 0x21},
{"5", 0x22}, {"6", 0x23}, {"7", 0x24}, {"8", 0x25}, {"9", 0x26},
{"0", 0x27}, {"Enter", 0x28}, {"Esc", 0x29}, {"Backspace", 0x2A},
{"Tab", 0x2B}, {"Space", 0x2C}, {"-", 0x2D}, {"=", 0x2E},
{"[", 0x2F}, {"]", 0x30}, {"\\", 0x31}, {"Non-US #", 0x32},
{";", 0x33}, {"'", 0x34}, {"`", 0x35}, {",", 0x36}, {".", 0x37},
{"/", 0x38}, {"CapsLock", 0x39}, {"F1", 0x3A}, {"F2", 0x3B},
{"F3", 0x3C}, {"F4", 0x3D}, {"F5", 0x3E}, {"F6", 0x3F},
{"F7", 0x40}, {"F8", 0x41}, {"F9", 0x42}, {"F10", 0x43},
{"F11", 0x44}, {"F12", 0x45}, {"PrintScreen", 0x46},
{"ScrollLock", 0x47}, {"Pause", 0x48}, {"Insert", 0x49},
{"Home", 0x4A}, {"PageUp", 0x4B}, {"Delete", 0x4C}, {"End", 0x4D},
{"PageDown", 0x4E}, {"RightArrow", 0x4F}, {"LeftArrow", 0x50},
{"DownArrow", 0x51}, {"UpArrow", 0x52}, {"NumLock", 0x53},
{"KP /", 0x54}, {"KP *", 0x55}, {"KP -", 0x56}, {"KP +", 0x57},
{"KP Enter", 0x58}, {"KP 1", 0x59}, {"KP 2", 0x5A}, {"KP 3", 0x5B},
{"KP 4", 0x5C}, {"KP 5", 0x5D}, {"KP 6", 0x5E}, {"KP 7", 0x5F},
{"KP 8", 0x60}, {"KP 9", 0x61}, {"KP 0", 0x62}, {"KP .", 0x63},
{"Non-US \\ ", 0x64}, {"Application", 0x65}, {"Power", 0x66},
{"KP =", 0x67}, {"F13", 0x68}, {"F14", 0x69}, {"F15", 0x6A},
{"F16", 0x6B}, {"F17", 0x6C}, {"F18", 0x6D}, {"F19", 0x6E},
{"F20", 0x6F}, {"F21", 0x70}, {"F22", 0x71}, {"F23", 0x72},
{"F24", 0x73}, {"Execute", 0x74}, {"Help", 0x75}, {"Menu", 0x76},
{"Select", 0x77}, {"Stop", 0x78}, {"Again", 0x79}, {"Undo", 0x7A},
{"Cut", 0x7B}, {"Copy", 0x7C}, {"Paste", 0x7D}, {"Find", 0x7E},
{"Mute", 0x7F}, {"VolumeUp", 0x80}, {"VolumeDown", 0x81},
{"LockingCapsLock", 0x82}, {"LockingNumLock", 0x83},
{"LockingScrollLock", 0x84}, {"KP ,", 0x85}, {"KP =", 0x86},
{"International1", 0x87}, {"International2", 0x88},
{"International3", 0x89}, {"International4", 0x8A},
{"International5", 0x8B}, {"International6", 0x8C},
{"International7", 0x8D}, {"International8", 0x8E},
{"International9", 0x8F}, {"LANG1", 0x90}, {"LANG2", 0x91},
{"LANG3", 0x92}, {"LANG4", 0x93}, {"LANG5", 0x94}, {"LANG6", 0x95},
{"LANG7", 0x96}, {"LANG8", 0x97}, {"LANG9", 0x98}, {"AlternateErase", 0x99},
{"SysReq/Attention", 0x9A}, {"Cancel", 0x9B}, {"Clear", 0x9C},
{"Prior", 0x9D}, {"Return", 0x9E}, {"Separator", 0x9F}, {"Out", 0xA0},
{"Oper", 0xA1}, {"Clear/Again", 0xA2}, {"CrSel/Props", 0xA3},
{"ExSel", 0xA4},
// Additional key codes for media controls, etc.
{"AudioMute", 0xE2}, {"AudioVolumeUp", 0xE9}, {"AudioVolumeDown", 0xEA},
{"MediaNext", 0xB5}, {"MediaPrevious", 0xB6}, {"MediaStop", 0xB7},
{"MediaPlayPause", 0xCD}, {"LaunchMail", 0x18A}, {"LaunchCalculator", 0x192},
{"LaunchMyComputer", 0x194}, {"WWWSearch", 0x221}, {"WWWHome", 0x223},
{"WWWBack", 0x224}, {"WWWForward", 0x225}, {"WWWStop", 0x226},
{"WWWRefresh", 0x227}, {"WWWFavorites", 0x22A},
};
// WiFi credentials
const char* ssid = "YOUR_WIFI_NAME";
const char* password = "YOUR_WIFI_PASSWORD";
// Create an HTTP server
WiFiServer server(80);
// BLE HID device and input report characteristic
BLEHIDDevice* hid;
BLECharacteristic* input;
BLEServer *pServer = NULL;
bool deviceConnected = false;
// HID report map for a simple keyboard
const uint8_t HID_KEYBOARD_REPORT_MAP[] = {
0x05, 0x01, // Usage Page (Generic Desktop)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x85, 0x01, // Report ID (1)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0xE0, // Usage Minimum (Left Control)
0x29, 0xE7, // Usage Maximum (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, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x03, // Input (Constant)
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, Variable, Absolute)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x03, // Output (Constant)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Keyboard/Keypad)
0x19, 0x00, // Usage Minimum (Reserved (no event indicated))
0x29, 0x65, // Usage Maximum (Keyboard Application)
0x81, 0x00, // Input (Data, Array)
0xC0 // End Collection
};
// 定义LED引脚(通常为GPIO 2)
const int ledPin = 2;
// 回调类,用于处理连接和断开连接事件
class MyServerCallbacks : public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
Serial.println("Device connected");
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
Serial.println("Device disconnected, restarting advertising");
pServer->startAdvertising(); // 重新启动广告
}
};
void setup() {
Serial.begin(115200);
// Connect to WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
Serial.println("IP Address: ");
Serial.println(WiFi.localIP());
// Start the HTTP server
server.begin();
// Initialize BLE device
BLEDevice::init("XIAOCAICAI Keyboard");
pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks()); // 设置回调函数
// Create BLE HID device and input report characteristic
hid = new BLEHIDDevice(pServer);
input = hid->inputReport(1); // Input report ID = 1
// Set manufacturer and PnP info
hid->manufacturer()->setValue("XIAOCAICAI");
hid->pnp(0x02, 0xe502, 0xa111, 0x0210);
hid->hidInfo(0x00, 0x01);
// Set HID report map
hid->reportMap((uint8_t*)HID_KEYBOARD_REPORT_MAP, sizeof(HID_KEYBOARD_REPORT_MAP));
hid->startServices();
// Start advertising the BLE service
BLEAdvertising *pAdvertising = pServer->getAdvertising();
pAdvertising->setAppearance(HID_KEYBOARD);
pAdvertising->addServiceUUID(hid->hidService()->getUUID());
pAdvertising->start();
// Set initial battery level
hid->setBatteryLevel(100);
// 初始化LED引脚为输出
pinMode(ledPin, OUTPUT);
}
void loop() {
// Handle HTTP client connections
WiFiClient client = server.available();
if (client) {
Serial.println("New Client.");
String currentLine = "";
String request = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.write(c);
request += c; // Accumulate the request
if (c == '\n' && currentLine.length() == 0) {
// End of HTTP headers
Serial.println("Request received:");
Serial.println(request);
// Send a response to the client
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/plain");
client.println();
// Parse the request for the key
int keyIndex = request.indexOf("/");
if (keyIndex >= 0) {
keyIndex += 1; // Skip to the key value
String key = request.substring(keyIndex, keyIndex + 1);
Serial.println("Key received: " + key);
// Send key via BLE
sendKey(key);
} else {
Serial.println("Key not found in request");
}
client.println();
break;
} else if (c != '\r') {
currentLine += c;
} else {
currentLine = "";
}
}
}
client.stop();
Serial.println("Client Disconnected.");
}
}
// Function to send a key via BLE Keyboard
void sendKey(String key) {
uint8_t keyCode = 0;
// Look up the key code in the map
std::string stdKey = key.c_str(); // Convert Arduino String to std::string
auto it = keyMap.find(stdKey);
if (it != keyMap.end()) {
keyCode = it->second;
} else {
Serial.println("Key not found: " + key);
return;
}
uint8_t report[8] = {0};
report[2] = keyCode;
input->setValue(report, sizeof(report));
input->notify();
// 控制LED闪烁
digitalWrite(ledPin, HIGH); // 打开LED
delay(100); // Small delay to simulate key press duration
digitalWrite(ledPin, LOW); // 关闭LED
// Release the key
report[2] = 0;
input->setValue(report, sizeof(report));
input->notify();
}
基础的POC搞定了,期待一下后面更多自动化有意思的小玩意。