Press "Enter" to skip to content

使用ESP32基于API的模拟键盘操作

背景

最近在打魔兽,玩的服务器需要挂机,需要执行一些自动化的操作。

虽然之前有事件过可以通过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搞定了,期待一下后面更多自动化有意思的小玩意。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注