To enhance security, user behavior data (telemetry) are collected only on critical pages like login and register. This allows us to focus verification efforts on areas with heightened risk of bot activity. Specifically, a unique telemetry hash is calculated after credentials are submitted via a POST request.
The algorithm runs the SendRequestData function which for each POST action computes the hash of
. After retrieving necessary data and initializing variables, entry data are analyzed, concerning whether they represent mouse or keyboard. For keyboard, time between key presses is additionally analyzed. Finally, computed hash and updated time strings are sent to the database.
Algorithm 2 Telemetry Data Hash |
function SendRequestData Current datetime Request headers as dictionary ‘N/A’ Initialize database cursor if Request method is ‘POST’ then Retrieve telemetry data between interaction start and current timestamp Fetched data if is not empty then Initialize , , , for each entry d in do if is ‘mouse’ then Append mouse coordinates to else if is ‘keyboard’ then Add to end if end for Calculate average time between key presses Append average time to Compute hash of end if Insert request data into the database end if Commit changes to the database Close database connection end function
|
Attack Execution
The attack begins by stealthily gathering crucial information about the target login page. This involves pinpointing the exact positions of input boxes and the submit button, as meticulously outlined in Algorithm 3.
Algorithm 3 Get Initial Position of Element |
function GetInitialPosition(host, path, element_id, offset) Initialize headless Chrome browser with fullscreen and SSL error ignore options Navigate browser to “https://{host}{path}” Find the element on the webpage by its ID Get the screen dimensions using PyAutoGUI Retrieve browser window position Calculate the center coordinates of the element Adjust coordinates to be within screen limits and apply offset Quit the browser return Screen coordinates (screen_x, screen_y) end function
|
The algorithm triggers the GetInitialPosition function, which contains several elements that are launched sequentially. The goal is to find coordinates of the elements placed on the web page.
Armed with this knowledge, the attack commences by sending a carefully crafted stream of POST requests. Each request bears a deceptive payload: meticulously crafted mouse position data, designed to mimic genuine human interaction. This process, detailed in Algorithm 4, aims to evade detection by blending seamlessly with typical user behavior.
The algorithm triggers the SendTelemetryRequest function, which constructs data as a JSON string, which is equipped with coordinates and host, session, and browser details. Finally the string is sent with path, data, and necessary headers.
Algorithm 4 Send Telemetry Request |
function SendTelemetryRequest(event_type, timestamp, x (optional), y (optional)) Define path as ‘/track_telemetry’ Construct data as a JSON string with event_type, timestamp, x, and y Define headers with necessary information including host, session, and browser details Call send_post_request with path, data, and headers end function
|
Mouse positions are calculated, leveraging Bézier curves, widely used in computer graphics, game development, and image processing, to generate realistic mouse trajectories. These curves offer flexibility and smooth transitions, crucial for mimicking natural user behavior. It is important to understand that those curves can be exchanged with new ML models, such as “SapiAgent”, to generate even more human-like mouse trajectories [
17]. However, for simplicity, we will operate on the Bézier curves. They are defined by Bernstein polynomials as shown in Equation (
1) [
18],
where:
We strategically choose three control points for each curve:
This point-selection process is elucidated in Algorithm 5.
Algorithm 5 Generate Random Point on Line |
- 1:
function GenerateRandomPointOnLine(point_a, point_b, randomness) - 2:
Random number between 0 and 1 - 3:
- 4:
- 5:
Calculate the directional difference , between and - 6:
Determine perpendicular direction , - 7:
Normalize the perpendicular direction - 8:
Calculate based on - 9:
Adjust x, y coordinates by applying in the perpendicular direction - 10:
return x, y - 11:
end function
|
By leveraging Equation (
1), we numerically calculate the Bézier curve points, as outlined in Algorithm 6.
Algorithm 6 Generate Bézier Curve Points |
- 1:
function GenerateBezierCurve(control_points, num_points) - 2:
Create a list of evenly spaced values between 0 and 1 - 3:
Initialize as a zero matrix - 4:
Number of control points minus 1 - 5:
for to do - 6:
for to n do - 7:
Calculate Bernstein polynomial - 8:
- 9:
end for - 10:
end for - 11:
return - 12:
end function
|
To visualize the generated curves and enhance understanding, we present them in
Figure 1.
Once the simulated mouse reaches its intended input field, the attack shifts its focus to simulating keyboard activity. This attack leverages the assumption that attackers can readily build a database of keyboard dynamics for their use. To simulate real user behavior, a simple keylogger was implemented. The code samples 128 random passwords from the “rockyou.txt” file [
19], one at a time. For each password, it meticulously records every keystroke, capturing both the exact moment the key is pressed (key_down) and released (key_up). These precise timing data continue until the user presses “Enter”, signaling the completion of the typed word. Captured data are then stored in a comma-separated value (CSV) file. The structure of this database is outlined in
Table 1.
Each row uniquely identifies a keystroke with a timestamp (recorded in milliseconds), and associates it with the typed word (using an internal Word_ID) and the corresponding ASCII code.
A Long Short-Term Memory (LSTM) model serves as the backbone for predicting the intervals between keystrokes [
5]. This choice leverages the inherent sequential nature of keystroke timing data, allowing the model to capture and learn crucial temporal dependencies. While our problem was quite simple, we used one hidden layer. A dense layer with 10 nodes per layer was implemented. The dropout value was set to 20%. This value is widely accepted as the best compromise between preventing model overfitting and retaining model accuracy. A uniform distribution was used to choose initial weight values. Decay rate was set to the default value of 0.97.
Figure 2 visually represents the model’s structure. The model loss is shown on
Figure 3.
Specifically, the model uses 34 input features, representing the maximum password length from the generated database. This allows the model to understand the potential range of keystroke sequences. The LSTM layer then processes this information efficiently, extracting appropriate patterns and relationships from data. Finally, the output layer utilizes these learned patterns to predict the time a key should be held for, as well as the interval between the current and next keystroke.
Algorithm 7 precisely predicts key press and release timings, ensuring a natural typing cadence. These meticulously timed events are then transmitted to the server, further solidifying the illusion of a human presence.
Algorithm 7 Type In Current Input Field |
function TypeInCurrentField(word) global timestamp times ← predict_hold_and_release_times(word) for each time in times do offset ← create time delta from time[0] send_telemetry_request(“keyboard”, timestamp + offset) increment timestamp by offset and additional time from time[1] end for end function
|
After successfully filling each input field, the attack seamlessly resumes its simulated mouse movements, guiding the cursor towards the submit button. Upon reaching this critical element, the attack culminates in the final submit button click. To maximize the likelihood of success, the attack relentlessly repeats this intricate sequence, relentlessly attempting passwords from the expansive “rockyou.txt” file. This unwavering persistence ensures that no potential password combination is overlooked.