diff --git a/Upstream/Inc/build_config.h b/Upstream/Inc/build_config.h index 48a7870..f402eaf 100644 --- a/Upstream/Inc/build_config.h +++ b/Upstream/Inc/build_config.h @@ -25,17 +25,16 @@ //Configure keyboard rate-limiting (bot detection) here: #ifdef CONFIG_KEYBOARD_BOT_DETECT_ENABLED - #define KEYBOARD_BOTDETECT_FASTKEY_TIME_MS 125 //Alphanumeric keys (& space & shift & comma & fullstop), used for high-speed typing. Limit 8 per second - #define KEYBOARD_BOTDETECT_FASTKEY_TIME_REPEATED_MS 170 //Alphanumeric keys pressed repeatedly. Limit 6 keys per second. - #define KEYBOARD_BOTDETECT_SLOWKEY_TIME_MS 200 //Non-alphanumeric keys. Limit 5 per second. - #define KEYBOARD_BOTDETECT_SLOWKEY_TIME_REPEATED_MS 170 //Non-alphanumeric key, pressed repeatedly. Limit 6 keys per second. - #define KEYBOARD_BOTDETECT_MINIMUM_KEYDOWN_TIME_MS 30 //Key pressed less than this indicates bot. This value must be less than the smallest of the above ratelimit key times for the code to work correctly! + #define KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS 10 //10ms per bin + #define KEYBOARD_BOTDETECT_SLOW_BIN_WIDTH_MS 20 //20ms per bin + #define KEYBOARD_BOTDETECT_FAST_BIN_COUNT 25 //25 bins at 10ms = 250ms fast coverage + #define KEYBOARD_BOTDETECT_SLOW_BIN_COUNT 50 //50 bins at 20ms = 1 sec slow coverage, wrapped - #define KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_KEY_COUNT 3 //'Burst' = maximum number of keys with active timer before triggering temporary lockout - #define KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_KEY_COUNT 5 //'Burst' = maximum number of keys with active timer before triggering permanent lockout - #define KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_SHORT_COUNT 3 //One short keypress causes temporary lockout. 3 short keypresses within the temporary lockout time triggers permanent lockout + #define KEYBOARD_BOTDETECT_FAST_BIN_DRAIN_DIVIDER 2 + #define KEYBOARD_BOTDETECT_SLOW_BIN_DRAIN_DIVIDER 4 + #define KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD 4 - #define KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_TIME_MS 5000 //Lockout for 5 seconds when temporary lockout threshold reached + #define KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_TIME_MS 4000 #define KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_FLASH_TIME_MS 60000 //Flash fault LED for 60 seconds after temporary lockout #endif diff --git a/Upstream/Inc/upstream_hid_botdetect.h b/Upstream/Inc/upstream_hid_botdetect.h index 89942b7..e5bf9d2 100644 --- a/Upstream/Inc/upstream_hid_botdetect.h +++ b/Upstream/Inc/upstream_hid_botdetect.h @@ -12,19 +12,13 @@ #include "stm32f4xx_hal.h" -#define KEY_ROLLOVER 0x01 //Rollover means we hold the last reported key status +#define KEY_ROLLOVER 0x01 //Rollover means we hold the last reported key status #define KEY_A 0x04 -#define KEY_Z 0x1D -#define KEY_1 0x1E -#define KEY_0 0x27 -#define KEY_SPACE 0x2C -#define KEY_COMMA 0x36 -#define KEY_FULLSTOP 0x37 -#define KEY_PAD_1 0x59 -#define KEY_PAD_0 0x62 -#define KEY_MODIFIER_BASE 0xE0 //First modifier key is L-Ctl -#define KEY_MODIFIER_SHIFT_L 0xE1 -#define KEY_MODIFIER_SHIFT_R 0xE5 +#define KEY_MODIFIER_BASE 0xE0 //First modifier key is L-Ctl + + +#define KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS 14 //This is here because it is not a tuneable parameter + typedef enum @@ -40,8 +34,7 @@ LockoutStateTypeDef; typedef struct { uint8_t KeyCode; - uint32_t KeyDownTime; - uint32_t KeyActiveTimer; + uint32_t KeyDownStart; } KeyTimerLogTypeDef; diff --git a/Upstream/Src/upstream_hid_botdetect.c b/Upstream/Src/upstream_hid_botdetect.c index 7db6480..6fe0fe2 100644 --- a/Upstream/Src/upstream_hid_botdetect.c +++ b/Upstream/Src/upstream_hid_botdetect.c @@ -24,11 +24,24 @@ volatile LockoutStateTypeDef LockoutState = LOCKOUT_STATE_INACTIVE; //Variables specific to keyboard bot detection: #if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) - KeyTimerLogTypeDef KeyTimerLog[KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_KEY_COUNT]; - volatile uint8_t KeyTimerCount = 0; - uint8_t LastKeyCode = 0; - uint8_t ShortKeypressCount = 0; - uint8_t OldKeyboardInData[HID_KEYBOARD_INPUT_DATA_LEN] = {0}; + uint32_t LastKeyDownTime = 0; + KeyTimerLogTypeDef KeyTimerLog[KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS] = {0}; + + uint8_t KeyDelayFastBinDrainDivideCount = 0; + uint8_t KeyDelaySlowBinDrainDivideCount = 0; + uint8_t KeyDowntimeFastBinDrainDivideCount = 0; + uint8_t KeyDowntimeSlowBinDrainDivideCount = 0; + uint8_t KeyDelayFastBinArray[KEYBOARD_BOTDETECT_FAST_BIN_COUNT] = {0}; + uint8_t KeyDelaySlowBinArray[KEYBOARD_BOTDETECT_SLOW_BIN_COUNT] = {0}; + uint8_t KeyDowntimeFastBinArray[KEYBOARD_BOTDETECT_FAST_BIN_COUNT] = {0}; + uint8_t KeyDowntimeSlowBinArray[KEYBOARD_BOTDETECT_SLOW_BIN_COUNT] = {0}; + uint8_t OldKeyboardInData[HID_KEYBOARD_INPUT_DATA_LEN] = {0}; + + //Debug: +// uint8_t KeyDelayFastBinArrayPeak; +// uint8_t KeyDelaySlowBinArrayPeak; +// uint8_t KeyDowntimeFastBinArrayPeak; +// uint8_t KeyDowntimeSlowBinArrayPeak; #endif @@ -43,14 +56,15 @@ volatile LockoutStateTypeDef LockoutState = LOCKOUT_STATE_INACTIVE; //Code specific to keyboard bot detection: #if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) -static uint32_t Upstream_HID_BotDetectKeyboard_RolloverCheck(uint8_t* keyboardInData); -static uint32_t Upstream_HID_BotDetectKeyboard_KeyIsFast(uint8_t keyCode); -static void Upstream_HID_BotDetectKeyboard_KeyDown(uint8_t keyCode); -static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode); +static uint32_t Upstream_HID_BotDetectKeyboard_RolloverCheck(uint8_t* keyboardInData); +static void Upstream_HID_BotDetectKeyboard_DoLockout(void); +static void Upstream_HID_BotDetectKeyboard_KeyDown(uint8_t keyCode); +static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode); + //Checks if received keyboard data is from a real human. -//This is not entirely bulletproof as an attacking device may slow its keypresses below the threshold. +//This is not entirely bulletproof as an attacking device may randomize its keypresses. void Upstream_HID_BotDetectKeyboard(uint8_t* keyboardInData) { uint32_t i; @@ -108,19 +122,38 @@ void Upstream_HID_BotDetectKeyboard(uint8_t* keyboardInData) } } - //Copy new data to old array - for (i = 0; i < HID_KEYBOARD_INPUT_DATA_LEN; i++) + //Check for evidence of bot typing + for (i = 0; i < KEYBOARD_BOTDETECT_FAST_BIN_COUNT; i++) { - OldKeyboardInData[i] = keyboardInData[i]; + if ((KeyDelayFastBinArray[i] > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD) || + (KeyDowntimeFastBinArray[i] > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD)) + { + Upstream_HID_BotDetectKeyboard_DoLockout(); + break; + } + + //Debug: +// if (KeyDelayFastBinArray[i] > KeyDelayFastBinArrayPeak) KeyDelayFastBinArrayPeak = KeyDelayFastBinArray[i]; +// if (KeyDowntimeFastBinArray[i] > KeyDowntimeFastBinArrayPeak) KeyDowntimeFastBinArrayPeak = KeyDowntimeFastBinArray[i]; } + for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++) + { + if ((KeyDelaySlowBinArray[i] > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD) || + (KeyDowntimeSlowBinArray[i] > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD)) + { + Upstream_HID_BotDetectKeyboard_DoLockout(); + break; + } - //Final checks - if ((KeyTimerCount > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_KEY_COUNT) && - (LockoutState != LOCKOUT_STATE_PERMANENT_ACTIVE)) + //Debug: +// if (KeyDelaySlowBinArray[i] > KeyDelaySlowBinArrayPeak) KeyDelaySlowBinArrayPeak = KeyDelaySlowBinArray[i]; +// if (KeyDowntimeSlowBinArray[i] > KeyDowntimeSlowBinArrayPeak) KeyDowntimeSlowBinArrayPeak = KeyDowntimeSlowBinArray[i]; + } + + //Copy new data to old array + for (i = 0; i < HID_KEYBOARD_INPUT_DATA_LEN; i++) { - TemporaryLockoutTimeMs = 0; - LockoutState = LOCKOUT_STATE_TEMPORARY_ACTIVE; - LED_SetState(LED_STATUS_FLASH_BOTDETECT); + OldKeyboardInData[i] = keyboardInData[i]; } //Host receives no data if we are locked @@ -136,6 +169,39 @@ void Upstream_HID_BotDetectKeyboard(uint8_t* keyboardInData) +static void Upstream_HID_BotDetectKeyboard_DoLockout(void) +{ + uint32_t i; + + if (LockoutState == LOCKOUT_STATE_PERMANENT_ACTIVE) return; + + //Are we already in warning state? -> activate permanent lockout + if ((LockoutState == LOCKOUT_STATE_TEMPORARY_ACTIVE) || + (LockoutState == LOCKOUT_STATE_TEMPORARY_FLASHING)) + { + LockoutState = LOCKOUT_STATE_PERMANENT_ACTIVE; + return; + } + + //Otherwise, reset counters and give warning + for (i = 0; i < KEYBOARD_BOTDETECT_FAST_BIN_COUNT; i++) + { + KeyDelayFastBinArray[i] = 0; + KeyDowntimeFastBinArray[i] = 0; + } + for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++) + { + KeyDelaySlowBinArray[i] = 0; + KeyDowntimeSlowBinArray[i] = 0; + } + + TemporaryLockoutTimeMs = 0; + LockoutState = LOCKOUT_STATE_TEMPORARY_ACTIVE; + LED_SetState(LED_STATUS_FLASH_BOTDETECT); +} + + + //Keyboard reports a rollover code when there are too many keys to scan/report. static uint32_t Upstream_HID_BotDetectKeyboard_RolloverCheck(uint8_t* keyboardInData) { @@ -174,104 +240,100 @@ static uint32_t Upstream_HID_BotDetectKeyboard_RolloverCheck(uint8_t* keyboardIn static void Upstream_HID_BotDetectKeyboard_KeyDown(uint8_t keyCode) { - uint32_t tempKeyActiveTime; + uint32_t i; + uint32_t keyDelay; + uint32_t now = HAL_GetTick(); - if (KeyTimerCount >= KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_KEY_COUNT) + keyDelay = now - LastKeyDownTime; + if (keyDelay < (KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS * KEYBOARD_BOTDETECT_FAST_BIN_COUNT)) { - LockoutState = LOCKOUT_STATE_PERMANENT_ACTIVE; - LED_SetState(LED_STATUS_FLASH_BOTDETECT); - return; - } + KeyDelayFastBinArray[(keyDelay / KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS)]++; //Add key to fast bin - if (Upstream_HID_BotDetectKeyboard_KeyIsFast(keyCode)) - { - if (keyCode == LastKeyCode) + //Drain fast bins at specified rate + KeyDelayFastBinDrainDivideCount++; + if (KeyDelayFastBinDrainDivideCount >= KEYBOARD_BOTDETECT_FAST_BIN_DRAIN_DIVIDER) { - tempKeyActiveTime = KEYBOARD_BOTDETECT_FASTKEY_TIME_REPEATED_MS; - } - else - { - tempKeyActiveTime = KEYBOARD_BOTDETECT_FASTKEY_TIME_MS; + KeyDelayFastBinDrainDivideCount = 0; + for (i = 0; i < KEYBOARD_BOTDETECT_FAST_BIN_COUNT; i++) + { + if (KeyDelayFastBinArray[i] > 0) KeyDelayFastBinArray[i]--; + } } } else { - if (keyCode == LastKeyCode) - { - tempKeyActiveTime = KEYBOARD_BOTDETECT_SLOWKEY_TIME_REPEATED_MS; - } - else + keyDelay = keyDelay % (KEYBOARD_BOTDETECT_SLOW_BIN_WIDTH_MS * KEYBOARD_BOTDETECT_SLOW_BIN_COUNT); //Wrap slow key time into the slow array + KeyDelaySlowBinArray[(keyDelay / KEYBOARD_BOTDETECT_SLOW_BIN_WIDTH_MS)]++; //Add key to slow bin + + //Drain slow bins at specified rate + KeyDelaySlowBinDrainDivideCount++; + if (KeyDelaySlowBinDrainDivideCount >= KEYBOARD_BOTDETECT_SLOW_BIN_DRAIN_DIVIDER) { - tempKeyActiveTime = KEYBOARD_BOTDETECT_SLOWKEY_TIME_MS; + KeyDelaySlowBinDrainDivideCount = 0; + for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++) + { + if (KeyDelaySlowBinArray[i] > 0) KeyDelaySlowBinArray[i]--; + } } } - LastKeyCode = keyCode; - - //Disable systick interrupt while adding key to array - __disable_irq(); - KeyTimerLog[KeyTimerCount].KeyCode = keyCode; - KeyTimerLog[KeyTimerCount].KeyDownTime = HAL_GetTick(); - KeyTimerLog[KeyTimerCount].KeyActiveTimer = tempKeyActiveTime; - KeyTimerCount++; - __enable_irq(); -} - + LastKeyDownTime = now; - -static uint32_t Upstream_HID_BotDetectKeyboard_KeyIsFast(uint8_t keyCode) -{ - if ((keyCode >= KEY_A) && (keyCode <= KEY_Z)) return 1; -// if ((keyCode >= KEY_1) && (keyCode <= KEY_0)) return 1; - if ((keyCode >= KEY_PAD_1) && (keyCode <= KEY_PAD_0)) return 1; - - if (keyCode == KEY_SPACE) return 1; - if (keyCode == KEY_COMMA) return 1; - if (keyCode == KEY_FULLSTOP) return 1; - if (keyCode == KEY_MODIFIER_SHIFT_L) return 1; - if (keyCode == KEY_MODIFIER_SHIFT_R) return 1; - - //else: - return 0; + for (i = 0; i < KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS; i++) + { + if (KeyTimerLog[i].KeyCode == 0) break; + } + if (i >= KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS) while (1); //Totally should not happen + KeyTimerLog[i].KeyCode = keyCode; + KeyTimerLog[i].KeyDownStart = now; } + static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode) { uint32_t i; + uint32_t keyDowntime; - //Search through active key array to see if this key was pressed recently - __disable_irq(); - for (i = 0; i < KeyTimerCount; i++) + for (i = 0; i < KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS; i++) { if (KeyTimerLog[i].KeyCode == keyCode) break; } - if (i >= KeyTimerCount) - { - __enable_irq(); - return; - } + if (i >= KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS) while (1); //Totally should not happen - //Was this is a short keypress? - if ((int32_t)(HAL_GetTick() - KeyTimerLog[i].KeyDownTime) < KEYBOARD_BOTDETECT_MINIMUM_KEYDOWN_TIME_MS) + KeyTimerLog[i].KeyCode = 0; //Clear out the key entry + keyDowntime = HAL_GetTick() - KeyTimerLog[i].KeyDownStart; + if (keyDowntime < (KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS * KEYBOARD_BOTDETECT_FAST_BIN_COUNT)) { - __enable_irq(); - ShortKeypressCount++; - LED_SetState(LED_STATUS_FLASH_BOTDETECT); - if (ShortKeypressCount >= KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_SHORT_COUNT) + KeyDowntimeFastBinArray[(keyDowntime / KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS)]++; //Add key to fast bin + + //Drain fast bins at specified rate + KeyDowntimeFastBinDrainDivideCount++; + if (KeyDowntimeFastBinDrainDivideCount >= KEYBOARD_BOTDETECT_FAST_BIN_DRAIN_DIVIDER) { - LockoutState = LOCKOUT_STATE_PERMANENT_ACTIVE; + KeyDowntimeFastBinDrainDivideCount = 0; + for (i = 0; i < KEYBOARD_BOTDETECT_FAST_BIN_COUNT; i++) + { + if (KeyDowntimeFastBinArray[i] > 0) KeyDowntimeFastBinArray[i]--; + } } - else + } + else + { + keyDowntime = keyDowntime % (KEYBOARD_BOTDETECT_SLOW_BIN_WIDTH_MS * KEYBOARD_BOTDETECT_SLOW_BIN_COUNT); //Wrap slow key time into the slow array + KeyDowntimeSlowBinArray[(keyDowntime / KEYBOARD_BOTDETECT_SLOW_BIN_WIDTH_MS)]++; //Add key to slow bin + + //Drain slow bins at specified rate + KeyDowntimeSlowBinDrainDivideCount++; + if (KeyDowntimeSlowBinDrainDivideCount >= KEYBOARD_BOTDETECT_SLOW_BIN_DRAIN_DIVIDER) { - if (LockoutState != LOCKOUT_STATE_PERMANENT_ACTIVE) + KeyDowntimeSlowBinDrainDivideCount = 0; + for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++) { - TemporaryLockoutTimeMs = 0; - LockoutState = LOCKOUT_STATE_TEMPORARY_ACTIVE; + if (KeyDowntimeSlowBinArray[i] > 0) KeyDowntimeSlowBinArray[i]--; } } } - __enable_irq(); } #endif //if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) @@ -283,24 +345,8 @@ static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode) //Called by Systick_Handler every 1ms, at high interrupt priority. void Upstream_HID_BotDetect_Systick(void) { - uint32_t i; - //Keyboard-specific stuff: #if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) - if (KeyTimerCount > 0) //Check if current active key has timed out - { - if (KeyTimerLog[0].KeyActiveTimer-- == 0) - { - KeyTimerCount--; - for (i = 0; i < KeyTimerCount; i++) //Shuffle other keys down - { - KeyTimerLog[i].KeyCode = KeyTimerLog[i + 1].KeyCode; - KeyTimerLog[i].KeyDownTime = KeyTimerLog[i + 1].KeyDownTime; - KeyTimerLog[i].KeyActiveTimer = KeyTimerLog[i + 1].KeyActiveTimer; - } - } - } - //Check if temporary lockout has expired if (LockoutState == LOCKOUT_STATE_TEMPORARY_ACTIVE) {