Re-implement keyboard bot detection based on timing histograms, rather

than maximum typing speed.
USG_1.0
Robert Fisk 7 years ago
parent 441caf6768
commit 302bca3690

@ -25,17 +25,16 @@
//Configure keyboard rate-limiting (bot detection) here: //Configure keyboard rate-limiting (bot detection) here:
#ifdef CONFIG_KEYBOARD_BOT_DETECT_ENABLED #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_FAST_BIN_WIDTH_MS 10 //10ms per bin
#define KEYBOARD_BOTDETECT_FASTKEY_TIME_REPEATED_MS 170 //Alphanumeric keys pressed repeatedly. Limit 6 keys per second. #define KEYBOARD_BOTDETECT_SLOW_BIN_WIDTH_MS 20 //20ms per bin
#define KEYBOARD_BOTDETECT_SLOWKEY_TIME_MS 200 //Non-alphanumeric keys. Limit 5 per second. #define KEYBOARD_BOTDETECT_FAST_BIN_COUNT 25 //25 bins at 10ms = 250ms fast coverage
#define KEYBOARD_BOTDETECT_SLOWKEY_TIME_REPEATED_MS 170 //Non-alphanumeric key, pressed repeatedly. Limit 6 keys per second. #define KEYBOARD_BOTDETECT_SLOW_BIN_COUNT 50 //50 bins at 20ms = 1 sec slow coverage, wrapped
#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_TEMPORARY_LOCKOUT_KEY_COUNT 3 //'Burst' = maximum number of keys with active timer before triggering temporary lockout #define KEYBOARD_BOTDETECT_FAST_BIN_DRAIN_DIVIDER 2
#define KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_KEY_COUNT 5 //'Burst' = maximum number of keys with active timer before triggering permanent lockout #define KEYBOARD_BOTDETECT_SLOW_BIN_DRAIN_DIVIDER 4
#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_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 #define KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_FLASH_TIME_MS 60000 //Flash fault LED for 60 seconds after temporary lockout
#endif #endif

@ -14,17 +14,11 @@
#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_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_BASE 0xE0 //First modifier key is L-Ctl
#define KEY_MODIFIER_SHIFT_L 0xE1
#define KEY_MODIFIER_SHIFT_R 0xE5
#define KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS 14 //This is here because it is not a tuneable parameter
typedef enum typedef enum
@ -40,8 +34,7 @@ LockoutStateTypeDef;
typedef struct typedef struct
{ {
uint8_t KeyCode; uint8_t KeyCode;
uint32_t KeyDownTime; uint32_t KeyDownStart;
uint32_t KeyActiveTimer;
} }
KeyTimerLogTypeDef; KeyTimerLogTypeDef;

@ -24,11 +24,24 @@ volatile LockoutStateTypeDef LockoutState = LOCKOUT_STATE_INACTIVE;
//Variables specific to keyboard bot detection: //Variables specific to keyboard bot detection:
#if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) #if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED)
KeyTimerLogTypeDef KeyTimerLog[KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_KEY_COUNT]; uint32_t LastKeyDownTime = 0;
volatile uint8_t KeyTimerCount = 0; KeyTimerLogTypeDef KeyTimerLog[KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS] = {0};
uint8_t LastKeyCode = 0;
uint8_t ShortKeypressCount = 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}; uint8_t OldKeyboardInData[HID_KEYBOARD_INPUT_DATA_LEN] = {0};
//Debug:
// uint8_t KeyDelayFastBinArrayPeak;
// uint8_t KeyDelaySlowBinArrayPeak;
// uint8_t KeyDowntimeFastBinArrayPeak;
// uint8_t KeyDowntimeSlowBinArrayPeak;
#endif #endif
@ -44,13 +57,14 @@ volatile LockoutStateTypeDef LockoutState = LOCKOUT_STATE_INACTIVE;
#if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) #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_RolloverCheck(uint8_t* keyboardInData);
static uint32_t Upstream_HID_BotDetectKeyboard_KeyIsFast(uint8_t keyCode); static void Upstream_HID_BotDetectKeyboard_DoLockout(void);
static void Upstream_HID_BotDetectKeyboard_KeyDown(uint8_t keyCode); static void Upstream_HID_BotDetectKeyboard_KeyDown(uint8_t keyCode);
static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode); static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode);
//Checks if received keyboard data is from a real human. //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) void Upstream_HID_BotDetectKeyboard(uint8_t* keyboardInData)
{ {
uint32_t i; uint32_t i;
@ -108,19 +122,38 @@ void Upstream_HID_BotDetectKeyboard(uint8_t* keyboardInData)
} }
} }
//Copy new data to old array //Check for evidence of bot typing
for (i = 0; i < HID_KEYBOARD_INPUT_DATA_LEN; i++) 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;
} }
//Final checks //Debug:
if ((KeyTimerCount > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_KEY_COUNT) && // if (KeyDelayFastBinArray[i] > KeyDelayFastBinArrayPeak) KeyDelayFastBinArrayPeak = KeyDelayFastBinArray[i];
(LockoutState != LOCKOUT_STATE_PERMANENT_ACTIVE)) // if (KeyDowntimeFastBinArray[i] > KeyDowntimeFastBinArrayPeak) KeyDowntimeFastBinArrayPeak = KeyDowntimeFastBinArray[i];
}
for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++)
{ {
TemporaryLockoutTimeMs = 0; if ((KeyDelaySlowBinArray[i] > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD) ||
LockoutState = LOCKOUT_STATE_TEMPORARY_ACTIVE; (KeyDowntimeSlowBinArray[i] > KEYBOARD_BOTDETECT_TEMPORARY_LOCKOUT_BIN_THRESHOLD))
LED_SetState(LED_STATUS_FLASH_BOTDETECT); {
Upstream_HID_BotDetectKeyboard_DoLockout();
break;
}
//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++)
{
OldKeyboardInData[i] = keyboardInData[i];
} }
//Host receives no data if we are locked //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. //Keyboard reports a rollover code when there are too many keys to scan/report.
static uint32_t Upstream_HID_BotDetectKeyboard_RolloverCheck(uint8_t* keyboardInData) 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) 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; KeyDelayFastBinArray[(keyDelay / KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS)]++; //Add key to fast bin
LED_SetState(LED_STATUS_FLASH_BOTDETECT);
return;
}
if (Upstream_HID_BotDetectKeyboard_KeyIsFast(keyCode)) //Drain fast bins at specified rate
KeyDelayFastBinDrainDivideCount++;
if (KeyDelayFastBinDrainDivideCount >= KEYBOARD_BOTDETECT_FAST_BIN_DRAIN_DIVIDER)
{ {
if (keyCode == LastKeyCode) KeyDelayFastBinDrainDivideCount = 0;
for (i = 0; i < KEYBOARD_BOTDETECT_FAST_BIN_COUNT; i++)
{ {
tempKeyActiveTime = KEYBOARD_BOTDETECT_FASTKEY_TIME_REPEATED_MS; if (KeyDelayFastBinArray[i] > 0) KeyDelayFastBinArray[i]--;
} }
else
{
tempKeyActiveTime = KEYBOARD_BOTDETECT_FASTKEY_TIME_MS;
} }
} }
else else
{ {
if (keyCode == LastKeyCode) 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_REPEATED_MS; KeyDelaySlowBinDrainDivideCount = 0;
} for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++)
else
{ {
tempKeyActiveTime = KEYBOARD_BOTDETECT_SLOWKEY_TIME_MS; 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;
for (i = 0; i < KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS; i++)
static uint32_t Upstream_HID_BotDetectKeyboard_KeyIsFast(uint8_t keyCode)
{ {
if ((keyCode >= KEY_A) && (keyCode <= KEY_Z)) return 1; if (KeyTimerLog[i].KeyCode == 0) break;
// if ((keyCode >= KEY_1) && (keyCode <= KEY_0)) return 1; }
if ((keyCode >= KEY_PAD_1) && (keyCode <= KEY_PAD_0)) return 1; if (i >= KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS) while (1); //Totally should not happen
KeyTimerLog[i].KeyCode = keyCode;
if (keyCode == KEY_SPACE) return 1; KeyTimerLog[i].KeyDownStart = now;
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;
} }
static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode) static void Upstream_HID_BotDetectKeyboard_KeyUp(uint8_t keyCode)
{ {
uint32_t i; uint32_t i;
uint32_t keyDowntime;
//Search through active key array to see if this key was pressed recently for (i = 0; i < KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS; i++)
__disable_irq();
for (i = 0; i < KeyTimerCount; i++)
{ {
if (KeyTimerLog[i].KeyCode == keyCode) break; if (KeyTimerLog[i].KeyCode == keyCode) break;
} }
if (i >= KeyTimerCount) if (i >= KEYBOARD_BOTDETECT_MAX_ACTIVE_KEYS) while (1); //Totally should not happen
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(); KeyDowntimeFastBinArray[(keyDowntime / KEYBOARD_BOTDETECT_FAST_BIN_WIDTH_MS)]++; //Add key to fast bin
return;
}
//Was this is a short keypress? //Drain fast bins at specified rate
if ((int32_t)(HAL_GetTick() - KeyTimerLog[i].KeyDownTime) < KEYBOARD_BOTDETECT_MINIMUM_KEYDOWN_TIME_MS) KeyDowntimeFastBinDrainDivideCount++;
if (KeyDowntimeFastBinDrainDivideCount >= KEYBOARD_BOTDETECT_FAST_BIN_DRAIN_DIVIDER)
{ {
__enable_irq(); KeyDowntimeFastBinDrainDivideCount = 0;
ShortKeypressCount++; for (i = 0; i < KEYBOARD_BOTDETECT_FAST_BIN_COUNT; i++)
LED_SetState(LED_STATUS_FLASH_BOTDETECT);
if (ShortKeypressCount >= KEYBOARD_BOTDETECT_PERMANENT_LOCKOUT_SHORT_COUNT)
{ {
LockoutState = LOCKOUT_STATE_PERMANENT_ACTIVE; if (KeyDowntimeFastBinArray[i] > 0) KeyDowntimeFastBinArray[i]--;
}
}
} }
else else
{ {
if (LockoutState != LOCKOUT_STATE_PERMANENT_ACTIVE) 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)
{ {
TemporaryLockoutTimeMs = 0; KeyDowntimeSlowBinDrainDivideCount = 0;
LockoutState = LOCKOUT_STATE_TEMPORARY_ACTIVE; for (i = 0; i < KEYBOARD_BOTDETECT_SLOW_BIN_COUNT; i++)
{
if (KeyDowntimeSlowBinArray[i] > 0) KeyDowntimeSlowBinArray[i]--;
} }
} }
} }
__enable_irq();
} }
#endif //if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) #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. //Called by Systick_Handler every 1ms, at high interrupt priority.
void Upstream_HID_BotDetect_Systick(void) void Upstream_HID_BotDetect_Systick(void)
{ {
uint32_t i;
//Keyboard-specific stuff: //Keyboard-specific stuff:
#if defined (CONFIG_KEYBOARD_ENABLED) && defined (CONFIG_KEYBOARD_BOT_DETECT_ENABLED) #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 //Check if temporary lockout has expired
if (LockoutState == LOCKOUT_STATE_TEMPORARY_ACTIVE) if (LockoutState == LOCKOUT_STATE_TEMPORARY_ACTIVE)
{ {

Loading…
Cancel
Save