├── LICENSE ├── README.md ├── requirements.txt └── src ├── __init__.py ├── algorithms ├── __init__.py ├── custom_mse_function.py ├── first_algorithm.py └── variance_algorithm.py ├── extractor.py ├── lightgbm_classifier.py ├── old_mact ├── README.md ├── generate.go ├── generate_test.go ├── util.go └── util_test.go ├── playground ├── moving-average-transform.py ├── velocity_analysis.py └── visualize_points.py ├── predictor.py ├── randomforest_classifier.py └── samples ├── lightgbm_boostingtree.joblib ├── lightgbm_boostingtree_custom.joblib └── random_forest_model.joblib /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Akamai-Mouse-Protection 2 | 3 | # Description 4 | 5 | This is a POC of a Biometric Antibot Solution to stop mouse data generated by bots. This POC only works for the mouse movement generator shared in `src/old_mact.py`, and should not used for any other scope. The training data has not been shared, but only the polished model. 6 | 7 | To know more about the implementation, please refer to the blog post [here](https://atlaslogics.github.io). 8 | 9 | # Usage 10 | 11 | Be sure to load the `predictor.py` in a new virtual environment as the joblib library might not work as expected. 12 | 13 | ``` 14 | python3 -m venv venv 15 | source venv/bin/activate 16 | 17 | pip install -r requirements.txt 18 | 19 | python predictor.py 20 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | contourpy>=1.2.0 2 | cycler>=0.12.1 3 | fonttools>=4.47.2 4 | importlib-resources>=6.1.1 5 | joblib>=1.3.2 6 | kiwisolver>=1.4.5 7 | lightgbm>=4.3.0 8 | matplotlib>=3.8.2 9 | numpy>=1.26.3 10 | packaging>=23.2 11 | pandas>=2.2.0 12 | pillow>=10.2.0 13 | pyparsing>=3.1.1 14 | python-dateutil>=2.8.2 15 | pytz>=2024.1 16 | scikit-learn>=1.4.0 17 | scipy>=1.12.0 18 | six>=1.16.0 19 | threadpoolctl>=3.2.0 20 | tzdata>=2023.4 21 | zipp>=3.17.0 22 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIMIC-LOGICS/Akamai-Mouse-Protection/50d279b3bf7b747a6d9061e4670377b878470602/src/__init__.py -------------------------------------------------------------------------------- /src/algorithms/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIMIC-LOGICS/Akamai-Mouse-Protection/50d279b3bf7b747a6d9061e4670377b878470602/src/algorithms/__init__.py -------------------------------------------------------------------------------- /src/algorithms/custom_mse_function.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def custom_asymmetric_train(y_true, y_pred): 3 | try: 4 | residual = (y_true - y_pred).astype("float") 5 | # gradient of the MSE (y_true - y_pred)^2, https://www.geeksforgeeks.org/ml-gradient-boosting/ explain the negative sign 6 | grad = np.where(residual<0, -2*100.0*residual, -2*residual) 7 | hess = np.where(residual<0, 2*100.0, 2.0) 8 | except: 9 | residual = (y_true - y_pred.label).astype("float") 10 | grad = np.where(residual<0, -2*100.0*residual, -2*residual) 11 | hess = np.where(residual<0, 2*100.0, 2.0) 12 | return grad, hess 13 | -------------------------------------------------------------------------------- /src/algorithms/first_algorithm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import extractor 5 | from sklearn.metrics.pairwise import cosine_similarity 6 | from scipy.stats import pearsonr, spearmanr 7 | from sklearn.metrics import mean_squared_error 8 | from math import sqrt 9 | 10 | a = 0.955 11 | 12 | def calculate_smoothed_values(velocity, a): 13 | 14 | def smooth(step, prev, a): 15 | return (1-a)*step + a*prev 16 | 17 | smoothed_values = [0] 18 | 19 | # Reverse the EWMA formula 20 | x_approx = np.zeros_like(velocity) 21 | for n in range(1, len(velocity)): 22 | x_approx[n] = (velocity[n] - a * velocity[n-1]) / (1 - a) 23 | 24 | for i in range(0, len(velocity)): 25 | smoothed_values.append(smooth(x_approx[i], smoothed_values[i-1], a)) 26 | 27 | return smoothed_values 28 | 29 | def calculate_params(mact): 30 | 31 | _, X,Y,T = extractor.extract_coords_from_mact(mact) 32 | 33 | if len(X) == 0 or len(Y) == 0: 34 | return None 35 | 36 | X_VAL = X[:, 0] 37 | Y_VAL = Y[:, 0] 38 | 39 | velocityX = np.diff(X_VAL) 40 | velocityY = np.diff(Y_VAL) 41 | 42 | smoothed_valueX = calculate_smoothed_values(velocityX, a) 43 | smoothed_valueY = calculate_smoothed_values(velocityY, a) 44 | 45 | #apply pithagoras theorem 46 | smoothed_vel= np.sqrt(np.square(smoothed_valueX) + np.square(smoothed_valueY)) 47 | velocity_total = np.sqrt(np.square(velocityX) + np.square(velocityY)) 48 | 49 | if len(velocityX) != len(velocityY) or len(smoothed_vel) != len(velocity_total): 50 | print("Error: smoothed_vel and velocity_total are not the same length") 51 | return None 52 | 53 | #calculate pearson correlation between smoothed and total velocity 54 | pearson, _ = pearsonr(velocity_total, smoothed_vel) 55 | 56 | #calculate spearmans correlation between smoothed and total velocity 57 | corr, _ = spearmanr(velocity_total, smoothed_vel) 58 | 59 | #calculate cosine similarity between smoothed and total velocity 60 | cosine_similarity = cosine_similarity(velocity_total.reshape(1,-1), smoothed_vel.reshape(1,-1)) 61 | 62 | # calculate rmse between smoothed and total velocity 63 | rmse = sqrt(mean_squared_error(velocity_total, smoothed_vel)) 64 | 65 | return [pearson, corr, cosine_similarity[0][0], rmse] 66 | -------------------------------------------------------------------------------- /src/algorithms/variance_algorithm.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import extractor 5 | 6 | a = 0.955 # Smoothing factor used in the MACT 7 | 8 | def calculate_var(y,a): 9 | 10 | """ 11 | Reverse the EWMA formula to calculate the variance of the clusters 12 | @param y: the input signal 13 | @param a: the smoothing factor 14 | @return: the variance of the clusters 15 | """ 16 | 17 | # Reverse the EWMA formula 18 | x_approx = np.zeros_like(y) 19 | for n in range(1, len(y)): 20 | x_approx[n] = (y[n] - a * y[n-1]) / (1 - a) 21 | 22 | # We assume that the cluster size is 12 23 | cluster_size = 12 24 | 25 | variances = [] 26 | #measure the variance of each cluster in an 27 | for i in range(0, len(y), cluster_size): 28 | variances.append(np.var(x_approx[i:i+cluster_size])) 29 | 30 | return variances 31 | 32 | def calculate_params(mact): 33 | 34 | coords, X,Y,T = extractor.extract_coords_from_mact(mact) 35 | 36 | if len(X) == 0 or len(Y) == 0: 37 | return None 38 | 39 | X_VAL = X[:,0] 40 | Y_VAL = Y[:,0] 41 | 42 | velocityX = np.diff(X_VAL) 43 | velocityY = np.diff(Y_VAL) 44 | 45 | #if velocityX or velocityY contains NaN values, return None 46 | if np.isnan(velocityX).any() or np.isnan(velocityY).any(): 47 | return None 48 | 49 | smoothed_valueX = calculate_var(velocityX, a) 50 | smoothed_valueY = calculate_var(velocityY, a) 51 | 52 | #if smoothed_valueX or smoothed_valueY contains NaN values, return None 53 | if np.isnan(smoothed_valueX).any() or np.isnan(smoothed_valueY).any(): 54 | return None 55 | 56 | if len(smoothed_valueX) != len(smoothed_valueY): 57 | print("Error: smoothed_vel and velocity_total are not the same length") 58 | return None 59 | 60 | #concatenate into a single array 61 | smoothed_vel = np.concatenate([smoothed_valueX, smoothed_valueY]) 62 | 63 | return smoothed_vel 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/extractor.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def extract_coords_from_mact(mact): 5 | """ 6 | Extracts the coordinates from a mact string 7 | :param mact: mact string 8 | :return: coordinates, x, y, t 9 | """ 10 | 11 | #remove " character from the string 12 | mact = mact.replace('"', '') 13 | 14 | mact_parsed = str(mact).split(";") 15 | X = [] 16 | Y = [] 17 | T = [] 18 | coords = [] 19 | 20 | for i in range(0, np.size(mact_parsed) - 1): 21 | value = mact_parsed[i].split(",") 22 | 23 | # remove clicks 24 | if len(value) == 6: 25 | continue #remove click 26 | 27 | x = int(value[3]) 28 | y = int(value[4]) 29 | t = int(value[2]) 30 | 31 | T.append(t) 32 | X.append([x]) 33 | Y.append([y]) 34 | coords.append([x,y]) 35 | 36 | coords = np.asarray(coords) 37 | X = np.asarray(X) 38 | Y = np.asarray(Y) 39 | T = np.asarray(T) 40 | return coords, X, Y, T 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/lightgbm_classifier.py: -------------------------------------------------------------------------------- 1 | 2 | import lightgbm 3 | import numpy as np 4 | import pandas as pd 5 | import algorithms.variance_algorithm as ewma 6 | from sklearn.model_selection import train_test_split 7 | from sklearn.ensemble import RandomForestClassifier 8 | from sklearn.metrics import accuracy_score 9 | import joblib 10 | import numpy as np 11 | from sklearn.metrics import confusion_matrix 12 | from algorithms.custom_mse_function import custom_asymmetric_train 13 | 14 | def load_data(real_path, fake_path): 15 | """ 16 | Load the fake and real data, in mact format, from the files and combines them into a single dataframe. 17 | @param real_path: The path to the file containing the real data 18 | @param fake_path: The path to the file containing the fake data 19 | @return: A tuple containing the data and the labels 20 | """ 21 | # Process each file 22 | real_data_df = process_file(real_path, 0) 23 | fake_data_df = process_file(fake_path, 1) 24 | 25 | # Combine the real and fake data 26 | combined_df = pd.concat([real_data_df, fake_data_df], ignore_index=True) 27 | 28 | # Some values from calculate_params returnes np.inf, I replace them with np.nan and then drop all the nan3 29 | combined_df.replace([np.inf, -np.inf], np.nan, inplace=True) 30 | combined_df.dropna(inplace=True) 31 | 32 | assert np.all(np.isfinite(combined_df)), "Data contains non-finite values." 33 | 34 | X = combined_df.drop('Label', axis=1) # Data without the label 35 | y = combined_df['Label'] 36 | 37 | return X, y 38 | 39 | def process_file(file_path, label): 40 | """ 41 | Extracts the features from the file and returns a dataframe containing the features and the label. 42 | """ 43 | with open(file_path, 'r') as file: 44 | lines = file.readlines() 45 | data = [ewma.calculate_params(line) for line in lines if ewma.calculate_params(line) is not None and ewma.calculate_params(line).size > 0] 46 | df = pd.DataFrame(data) 47 | df['Label'] = label # Add a column for the label 48 | return df 49 | 50 | def build_gbm(X_train, X_valid, y_train, y_valid, objective=custom_asymmetric_train): 51 | """ 52 | @param X_train: The training data 53 | @param X_valid: The validation data 54 | @param y_train: The training labels 55 | @param y_valid: The validation labels 56 | 57 | @return: A trained lightgbm model 58 | """ 59 | gbm = lightgbm.LGBMClassifier() 60 | 61 | # updating objective function to custom 62 | gbm.set_params(**{'objective': objective}) 63 | 64 | # fitting model 65 | gbm.fit( 66 | X_train, 67 | y_train, 68 | eval_set=[(X_valid, y_valid)], 69 | ) 70 | 71 | return gbm 72 | 73 | def calculate_accuracy(y_valid, y_pred): 74 | """ 75 | Calculate the accuracy of the model and print the confusion matrix. 76 | @param y_valid: The validation labels 77 | @param y_pred: The predicted labels 78 | """ 79 | 80 | accuracy = accuracy_score(y_valid, y_pred.round()) 81 | 82 | print("Accuracy: %.2f%%" % (accuracy * 100.0)) 83 | 84 | #compute confusion matrix and save the model 85 | y_pred_binary = np.where(y_pred >= 0.5, 1, 0) 86 | 87 | # Compute confusion matrix 88 | 89 | conf_matrix = confusion_matrix(y_valid, y_pred_binary) 90 | 91 | print(conf_matrix) 92 | 93 | def main(): 94 | X,y = load_data('samples/real_10k.txt', 'samples/fake_10k.txt') 95 | 96 | # Split the data into training and testing sets 97 | X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.3, random_state=42) 98 | 99 | # Train the model with custom MSE 100 | 101 | gbm = build_gbm(X_train, X_valid, y_train, y_valid) 102 | 103 | y_pred = gbm.predict(X_valid) 104 | 105 | calculate_accuracy(y_valid, y_pred) 106 | 107 | model_filename = 'samples/lightgbm_boostingtree_custom.joblib' 108 | joblib.dump(gbm, model_filename) 109 | 110 | # Train the model without custom MSE 111 | gbm2 = build_gbm(X_train, X_valid, y_train, y_valid, objective='binary') 112 | 113 | y_pred = gbm2.predict(X_valid) 114 | 115 | calculate_accuracy(y_valid, y_pred) 116 | 117 | # Save the model 118 | model_filename = 'samples/lightgbm_boostingtree.joblib' 119 | joblib.dump(gbm, model_filename) 120 | 121 | if __name__ == "__main__": 122 | main() -------------------------------------------------------------------------------- /src/old_mact/README.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Old mact algorithm used to bypass Akamai 1.60, not not working anymore. 4 | 5 | # Author 6 | 7 | Unknown -------------------------------------------------------------------------------- /src/old_mact/generate.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | ) 7 | 8 | // GenerateEvents generates mouse events given the number of events to generate. 9 | // The inner width and height are used as a max boundary for X and Y coordinates. 10 | func GenerateEvents(n, innerWidth, innerHeight int) (movements [][2]float64, delays []int64) { 11 | const ( 12 | cycles = 3 13 | smoothing = 0.955 14 | ) 15 | 16 | arrX := smooth(generateLine(n, cycles), smoothing) 17 | minX, maxX := getLimitsX(float64(innerWidth)) 18 | arrX = rescale(arrX, minX, maxX) 19 | 20 | arrY := smooth(generateLine(n, cycles), smoothing) 21 | minY, maxY := getLimitsY(float64(innerHeight)) 22 | arrY = rescale(arrY, minY, maxY) 23 | 24 | movements = make([][2]float64, n) 25 | delays = generateDelays(n) 26 | for i := 0; i < n; i++ { 27 | movements[i] = [2]float64{arrX[i], arrY[i]} 28 | } 29 | return 30 | } 31 | 32 | func getLimitsX(innerWidth float64) (float64, float64) { 33 | const margin = 0.2 34 | randomXMin := createGaussian(innerWidth*margin, innerWidth*margin) 35 | randomXDiff := createGaussian(innerWidth*(1-2*margin), innerWidth*margin) 36 | 37 | min := randomXMin() 38 | for min < 0 || min > 3*innerWidth*margin { 39 | min = randomXMin() 40 | } 41 | 42 | diff := randomXDiff() 43 | for diff < innerWidth*(1-3*margin) || diff > innerWidth { 44 | diff = randomXDiff() 45 | } 46 | 47 | return min, min + diff 48 | } 49 | 50 | func getLimitsY(innerHeight float64) (float64, float64) { 51 | const margin = 0.2 52 | randomYMin := createGaussian(innerHeight*margin, innerHeight*margin) 53 | randomYDiff := createGaussian(innerHeight*(1-2*margin), innerHeight*margin) 54 | 55 | min := randomYMin() 56 | for min < 0 || min > 3*innerHeight*margin { 57 | min = randomYMin() 58 | } 59 | 60 | diff := randomYDiff() 61 | for diff < innerHeight*(1-3*margin) || diff > innerHeight { 62 | diff = randomYDiff() 63 | } 64 | 65 | return min, min + diff 66 | } 67 | 68 | func generateLine(size, cycles int) (result []float64) { 69 | const ( 70 | min float64 = 0 71 | max float64 = 1000 72 | ) 73 | 74 | result = make([]float64, size) 75 | for i := 0; i < size; i++ { 76 | result[i] = min 77 | } 78 | 79 | multiplier := 2 80 | for i := 0; i < cycles; i++ { 81 | randoms := make([]float64, 2+int(math.Ceil(float64((size-1)/(size/int(math.Pow(2, float64(cycles)))))))) 82 | for j := 0; j < len(randoms); j++ { 83 | randoms[j] = min + rand.Float64()*(max/float64(multiplier)) 84 | } 85 | 86 | segmentSize := math.Floor(float64(size / multiplier)) 87 | for j := 0; j < size; j++ { 88 | currentSegment := math.Floor(float64(j) / segmentSize) 89 | 90 | ratio := float64(j)/segmentSize - math.Floor(float64(j)/segmentSize) 91 | result[j] += interpolate(randoms[int(currentSegment)], randoms[int(currentSegment)+1], ratio) 92 | } 93 | multiplier *= 2 94 | } 95 | 96 | return 97 | } 98 | 99 | func smooth(arr []float64, smoothing float64) (result []float64) { 100 | result = make([]float64, len(arr)) 101 | result[0] = arr[0] 102 | for i := 1; i < len(arr); i++ { 103 | 104 | result[i] = (1-smoothing)*arr[i] + smoothing*result[i-1] 105 | } 106 | return 107 | } 108 | 109 | func rescale(arr []float64, min, max float64) (result []float64) { 110 | result = make([]float64, len(arr)) 111 | oldMin, oldMax := arr[0], arr[1] 112 | for i := 0; i < len(arr); i++ { 113 | if oldMin > arr[i] { 114 | oldMin = arr[i] 115 | } 116 | 117 | if oldMax < arr[i] { 118 | oldMax = arr[i] 119 | } 120 | } 121 | 122 | for i := 0; i < len(arr); i++ { 123 | result[i] = ((arr[i]-oldMin)/(oldMax-oldMin))*(max-min) + min 124 | } 125 | return 126 | } 127 | 128 | func generateDelays(n int) (result []int64) { 129 | result = make([]int64, n) 130 | randomStep := createGaussian(7.9, 0.47) 131 | 132 | for i := 0; i < n; i++ { 133 | t := randomStep() 134 | for t < 4 || t > 11.8 { 135 | t = randomStep() 136 | } 137 | result[i] = int64(t + math.Copysign(0.5, t)) 138 | } 139 | 140 | return 141 | } 142 | -------------------------------------------------------------------------------- /src/old_mact/generate_test.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import ( 4 | "math/rand" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | "log" 9 | "bytes" 10 | "fmt" 11 | ) 12 | 13 | func TestGenerateEvents(t *testing.T) { 14 | var builder strings.Builder 15 | var buf bytes.Buffer 16 | logger := log.New(&buf, "logger: ", log.Lshortfile) 17 | 18 | const n = 100 19 | movements, delays := GenerateEvents(n, 1920, 1800) 20 | var currentTime int64 21 | for i := 0; i < n; i++ { 22 | builder.WriteString(strconv.Itoa(i)) 23 | builder.WriteString(",1,") 24 | builder.WriteString(strconv.FormatInt(currentTime, 10)) 25 | builder.WriteString(",") 26 | builder.WriteString(strconv.Itoa(int(movements[i][0]))) 27 | builder.WriteString(",") 28 | builder.WriteString(strconv.Itoa(int(movements[i][1]))) 29 | builder.WriteString(";") 30 | 31 | currentTime += delays[i] 32 | } 33 | 34 | logger.Println(builder.String()) 35 | 36 | // At the end of the test, print the buffer's contents 37 | fmt.Println(buf.String())} 38 | 39 | func BenchmarkGenerateEvents(b *testing.B) { 40 | rand.Seed(165527284833122202) 41 | b.ResetTimer() 42 | 43 | for i := 0; i < b.N; i++ { 44 | GenerateEvents(100, 1920, 1080) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/old_mact/util.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | ) 7 | 8 | func interpolate[T float32 | float64](x, y, t T) T { 9 | return y*t + x*(1-t) 10 | } 11 | 12 | // createGaussian creates a gaussian function with the given mean and standard deviation. 13 | // 14 | // This function uses the Marsaglia polar method. Read more: 15 | // 16 | // https://en.wikipedia.org/wiki/Marsaglia_polar_method 17 | func createGaussian(mean, stdev float64) func() float64 { 18 | var y2 float64 19 | useLast := false 20 | 21 | return func() float64 { 22 | var y1 float64 23 | if useLast { 24 | y1 = y2 25 | useLast = false 26 | } else { 27 | var x1, x2, w float64 28 | 29 | for ok := true; ok; ok = w >= 1 { 30 | x1 = 2*rand.Float64() - 1 31 | x2 = 2*rand.Float64() - 1 32 | w = x1*x1 + x2*x2 33 | } 34 | 35 | w = math.Sqrt((-2 * math.Log(w)) / w) 36 | y1 = x1 * w 37 | y2 = x2 * w 38 | useLast = true 39 | } 40 | 41 | return mean + (stdev * y1) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/old_mact/util_test.go: -------------------------------------------------------------------------------- 1 | package mouse 2 | 3 | import ( 4 | "math/rand" 5 | "strings" 6 | "testing" 7 | "math" 8 | ) 9 | 10 | 11 | import ( 12 | "strconv" 13 | ) 14 | 15 | func TestGenRandom(t *testing.T) { 16 | var builder strings.Builder 17 | var size = 100 18 | var cycles = 3 19 | var multiplier = 2 20 | 21 | const ( 22 | min float64 = 0 23 | max float64 = 1000 24 | ) 25 | 26 | randoms := make([]float64, 2+int(math.Ceil(float64((size-1)/(size/int(math.Pow(2, float64(cycles)))))))) 27 | for j := 0; j < len(randoms); j++ { 28 | randoms[j] = min + rand.Float64()*(max/float64(multiplier)) 29 | // convert random to string 30 | str := strconv.FormatFloat(randoms[j], 'f', -1, 64) 31 | builder.WriteString(str + ",") 32 | } 33 | 34 | t.Log(builder.String()) 35 | } 36 | 37 | 38 | func TestGenLine(t *testing.T) { 39 | var builder strings.Builder 40 | var size = 100 41 | var cycles = 3 42 | var multiplier = 2 43 | var smoothing = 0.955 44 | 45 | const ( 46 | min float64 = 0 47 | max float64 = 1000 48 | ) 49 | var result = make([]float64, size) 50 | for i := 0; i < cycles; i++ { 51 | 52 | randoms := make([]float64, 2+int(math.Ceil(float64((size-1)/(size/int(math.Pow(2, float64(cycles)))))))) 53 | for j := 0; j < len(randoms); j++ { 54 | randoms[j] = min + rand.Float64()*(max/float64(multiplier)) 55 | } 56 | segmentSize := math.Floor(float64(size / multiplier)) 57 | for j := 0; j < size; j++ { 58 | currentSegment := math.Floor(float64(j) / segmentSize) 59 | //builder.WriteString(str + ",") 60 | 61 | 62 | ratio := float64(j)/segmentSize - math.Floor(float64(j)/segmentSize) 63 | result[j] += interpolate(randoms[int(currentSegment)], randoms[int(currentSegment)+1], ratio) 64 | 65 | // convert result to string 66 | str := strconv.FormatFloat(result[j], 'f', -1, 64) 67 | builder.WriteString(str + ",") 68 | } 69 | builder.WriteString("\n") 70 | builder.WriteString("\n") 71 | 72 | multiplier *= 2 73 | 74 | } 75 | var result1 = make([]float64, len(result)) 76 | 77 | result1[0] = result[0] 78 | for i := 1; i < len(result); i++ { 79 | result1[i] = (1-smoothing)*result[i] + smoothing*result1[i-1] 80 | str := strconv.FormatFloat(result1[i], 'f', -1, 64) 81 | builder.WriteString(str + ",") 82 | } 83 | 84 | 85 | t.Log(builder.String()) 86 | } -------------------------------------------------------------------------------- /src/playground/moving-average-transform.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | # Parameters 5 | a = 0.955 # Smoothing factor 6 | N1, N2 = 50, 75 # Step change points 7 | u1, u2, u3 = 2, 5, 3 # Step values 8 | 9 | # Generate the input signal 10 | x = np.ones(300) * u1 11 | x[200:212] = 5 12 | x[212:225] = 8 13 | x[225:237] = 2 14 | x[237:250] = 2 15 | x[250:262] = 5 16 | x[262:275] = 7 17 | x[275:282] = 10 18 | x[282:300] = 1 19 | 20 | # Apply the filter 21 | y = np.zeros_like(x) 22 | for n in range(1, len(x)): 23 | y[n] = a * y[n-1] + (1 - a) * x[n] 24 | 25 | # Plot 26 | plt.plot(x, label='Input') 27 | plt.plot(y, label='Filtered Output') 28 | plt.xlabel('Sample') 29 | plt.ylabel('Value') 30 | plt.legend() 31 | plt.title('Response of EWMA Filter to Step Inputs') 32 | plt.show() 33 | -------------------------------------------------------------------------------- /src/playground/velocity_analysis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.signal import savgol_filter 3 | import os, sys, json, datetime 4 | import matplotlib.pyplot as plt 5 | 6 | sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 7 | import extractor 8 | 9 | #enable prnting of long numpy arrays 10 | np.set_printoptions(threshold=np.inf) 11 | 12 | mact = \ 13 | '0,1,472,652,1025;1,1,479,649,1021;2,1,487,643,1011;3,1,496,634,990;4,1,503,622,968;5,1,531,578,899;6,1,535,558,867;7,1,544,538,831;8,1,551,518,799;9,1,559,496,765;10,1,568,478,737;11,1,575,452,695;12,1,584,436,669;13,1,591,412,633;14,1,600,398,607;15,1,607,372,563;16,1,615,358,537;17,1,624,338,499;18,1,631,324,469;19,1,640,310,435;20,1,647,304,421;21,1,655,295,394;22,1,664,287,374;23,1,671,283,354;24,1,679,279,337;25,1,688,277,325;26,1,696,276,314;27,1,705,274,306;28,1,712,274,293;29,1,719,274,288;30,1,728,274,283;31,1,735,274,280;32,1,743,275,278;33,1,752,275,276;34,1,759,276,274;35,1,768,276,273;36,1,775,277,271;37,1,783,278,270;38,1,791,279,268;39,1,800,280,266;40,1,807,283,263;41,1,816,286,259;42,1,824,290,255;43,1,832,294,250;44,1,840,299,246;45,1,847,304,242;46,1,855,306,241;47,1,863,310,239;48,1,871,313,238;49,1,879,314,238;50,1,887,315,238;51,1,896,317,238;52,1,904,320,240;53,1,912,324,242;54,1,920,329,246;55,1,928,338,253;56,1,935,352,266;57,1,943,366,278;58,1,951,376,288;59,1,959,389,301;60,1,968,398,310;61,1,976,408,326;62,1,984,420,339;63,1,992,431,357;64,1,1001,447,381;65,1,1008,461,401;66,1,1016,473,417;67,1,1023,485,430;68,1,1032,489,436;69,1,1039,493,441;70,1,1048,495,444;71,1,1055,496,445;72,1,1064,497,446;73,1,1079,497,447;74,1,1087,498,448;75,1,1096,498,450;76,1,1103,499,452;77,1,1111,500,456;78,1,1119,502,462;79,1,1127,504,470;80,1,1135,507,481;81,1,1143,511,496;82,1,1152,516,515;83,1,1160,524,541;84,1,1168,530,565;85,1,1176,536,591;86,1,1184,540,607;87,1,1192,546,632;88,1,1200,552,668;89,1,1208,560,704;90,1,1216,564,730;91,1,1224,568,752;92,1,1232,570,772;93,1,1239,572,794;94,1,1248,574,816;95,1,1255,574,833;96,1,1264,574,847;97,1,1272,574,865;98,1,1280,574,884;99,1,1287,572,904;' 14 | file = 'data.json' 15 | 16 | coords, X,Y,T = extractor.extract_coords_from_mact(mact) 17 | 18 | #print the value of X 19 | print(X[:, 0]) 20 | print(Y[:, 0]) 21 | 22 | # Compute the Euclidean distance between consecutive points 23 | distance = np.zeros(len(X) - 1) 24 | TIME_DIFFERENCES = np.zeros(len(X) - 1) # vector of time differences 25 | for i in range(len(X) - 1): 26 | dx = X[i+1] - X[i] 27 | dy = Y[i+1] - Y[i] 28 | dt = T[i+1] - T[i] 29 | 30 | TIME_DIFFERENCES[i] = dt 31 | distance[i] = np.sqrt(dx**2 + dy**2) 32 | 33 | 34 | # Compute the velocity between consecutive points 35 | velocities = distance / TIME_DIFFERENCES 36 | 37 | # Smooth the velocities to make the plot better 38 | velocities = savgol_filter(velocities, window_length=15, polyorder=2) 39 | 40 | data = [{'velocity': velocity, 'time': time} for velocity, time in zip(velocities, TIME_DIFFERENCES[:-1])] 41 | 42 | # Load existing data from the JSON file, if it exists 43 | try: 44 | with open(file, 'r') as f: 45 | existing_data = json.load(f) 46 | except FileNotFoundError: 47 | existing_data = [] 48 | 49 | # Separate the new data with a timestamp key and a list of dictionaries for the data 50 | new_data = {'timestamp': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'data': []} 51 | for datum in data: 52 | if datum not in existing_data: 53 | new_data['data'].append(datum) 54 | 55 | # Append the new data to the existing data 56 | all_data = existing_data.copy() 57 | if new_data['data']: 58 | all_data.append(new_data) 59 | 60 | # Save the updated data to the JSON file 61 | with open(file, 'w') as f: 62 | json.dump(all_data, f) 63 | 64 | # Plot the velocities 65 | plt.plot(T[1:], velocities) 66 | plt.show() -------------------------------------------------------------------------------- /src/playground/visualize_points.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | 3 | points_str1 = "164.6582131394224,162.84980305332041,161.0413929672184,159.2329828811164,157.42457279501446,155.61616270891244,153.80775262281045,151.99934253670847,150.19093245060645,148.38252236450447,146.57411227840248,144.76570219230047,142.9572921061985,141.1488820200965,139.3404719339945,137.53206184789252,135.7236517617905,133.91524167568852,132.10683158958653,130.29842150348455,128.49001141738256,126.68160133128058,124.87319124517859,123.0647811590766,121.25637107297459,119.4479609868726,117.63955090077062,115.83114081466861,114.02273072856661,112.21432064246463,110.40591055636264,108.59750047026066,106.78909038415865,104.98068029805665,103.17227021195467,101.36386012585268,99.5554500397507,97.74703995364871,95.9386298675467,94.13021978144471,92.32180969534272,90.51339960924074,88.70498952313875,86.89657943703675,85.08816935093475,83.27975926483276,81.47134917873078,79.66293909262879,77.85452900652679,76.0461189204248,74.2377088343228,81.63670663234885,89.03570443037489,96.43470222840094,103.83370002642698,111.23269782445303,118.63169562247906,126.03069342050503,133.42969121853108,140.82868901655712,148.22768681458317,155.6266846126092,163.02568241063523,170.4246802086613,177.82367800668732,185.2226758047134,192.6216736027394,200.02067140076545,207.4196691987915,214.81866699681746,222.21766479484353,229.61666259286955,237.0156603908956,244.41465818892163,251.81365598694768,259.2126537849737,266.6116515829998,274.0106493810258,281.40964717905183,288.8086449770779,296.207642775104,303.60664057313,311.00563837115595,318.40463616918197,325.80363396720804,333.2026317652341,340.60162956326013,348.00062736128615,355.3996251593122,362.79862295733824,370.19762075536426,377.59661855339033,384.9956163514164,392.3946141494424,399.7936119474684,407.1926097454944,414.59160754352047,421.99060534154654,429.38960313957256,436.78860093759863" 4 | points_str2 = "180.37809147438733,181.3434422658482,182.30879305730906,183.27414384876994,184.23949464023082,185.20484543169167,186.17019622315254,187.13554701461342,188.10089780607427,189.06624859753515,190.03159938899603,190.99695018045685,191.96230097191776,192.9276517633786,193.89300255483948,194.85835334630033,195.82370413776118,196.78905492922206,197.75440572068294,198.71975651214382,199.6851073036047,200.65045809506555,201.61580888652642,202.5811596779873,203.54651046944815,204.51186126090903,203.2257613439595,201.93966142700992,200.6535615100604,199.36746159311087,198.08136167616135,196.79526175921183,195.5091618422623,194.22306192531272,192.9369620083632,191.65086209141367,190.36476217446415,189.07866225751462,187.79256234056507,186.50646242361555,185.22036250666602,183.93426258971647,182.64816267276694,181.36206275581742,180.0759628388679,178.78986292191834,177.50376300496882,176.2176630880193,174.93156317106974,173.64546325412022,172.3593633371707,184.24288703249016,196.12641072780966,208.00993442312912,219.89345811844862,231.7769818137681,243.66050550908756,255.54402920440694,267.4275528997264,279.3110765950459,291.19460029036543,303.0781239856849,314.96164768100437,326.84517137632383,338.7286950716433,350.6122187669628,362.49574246228224,374.37926615760176,386.26278985292123,398.1463135482406,410.0298372435601,421.9133609388796,433.79688463419905,445.6804083295185,457.56393202483804,469.4474557201575,470.1366451310297,470.8258345419018,471.51502395277396,472.2042133636461,472.8934027745183,473.5825921853904,474.27178159626254,474.9609710071347,475.65016041800686,476.339349828879,477.0285392397511,477.71772865062326,478.40691806149545,479.0961074723676,479.7852968832397,480.4744862941119,481.16367570498403,481.85286511585616,482.5420545267283,483.2312439376004,483.9204333484726,484.6096227593448,485.29881217021693,485.9880015810891" 5 | # Convert the strings to lists of float points 6 | points1 = [float(point) for point in points_str1.split(",")] 7 | points2 = [float(point) for point in points_str2.split(",")] 8 | 9 | # Plot the points using matplotlib 10 | plt.plot(points1, 'o', label='Points 1') 11 | plt.plot(points2, 'x', label='Points 2') 12 | plt.xlabel('Index') 13 | plt.ylabel('Points') 14 | plt.title('1D Array of Points') 15 | plt.legend() 16 | plt.show() 17 | -------------------------------------------------------------------------------- /src/predictor.py: -------------------------------------------------------------------------------- 1 | import joblib 2 | import pandas as pd 3 | import algorithms.variance_algorithm as ewma 4 | import numpy as np 5 | from algorithms.custom_mse_function import custom_asymmetric_train 6 | 7 | """ 8 | This class is used to predict whether a given mouse movement is real or fake. 9 | @input: Mouse movement data using MACT format 10 | @output: Either Fake or Real 11 | """ 12 | 13 | # Function to process the input data (similar to calculate_params) 14 | def process_input(input_data): 15 | # Process the input_data using calculate_params or similar function 16 | # This should match the format used during training 17 | processed_data = ewma.calculate_params(input_data) 18 | return pd.DataFrame([processed_data]) 19 | 20 | # Load the saved model 21 | model_filename = 'samples/lightgbm_boostingtree_custom.joblib' 22 | clf = joblib.load(model_filename) 23 | 24 | # take input from user 25 | input_data = input("Enter the data: ") 26 | 27 | # Process the input and make a prediction 28 | processed_input = process_input(input_data) 29 | prediction = clf.predict(processed_input) 30 | 31 | print("Model's prediction:", prediction) 32 | # Output the prediction (1 or 0) 33 | if prediction[0] > 0.5: 34 | print("This is a Fake Mouse Movement From That Old MACT!") 35 | else: 36 | print("This is a Real Mouse Movement") 37 | -------------------------------------------------------------------------------- /src/randomforest_classifier.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import algorithms.variance_algorithm as ewma 3 | from sklearn.model_selection import train_test_split 4 | from sklearn.ensemble import RandomForestClassifier 5 | from sklearn.metrics import accuracy_score 6 | import joblib 7 | import numpy as np 8 | from lightgbm_classifier import load_data, process_file, calculate_accuracy 9 | 10 | def main(): 11 | real_data_file = 'samples/real_10k.txt' 12 | fake_data_file = 'samples/fake_10k.txt' 13 | 14 | X,y = load_data(real_data_file, fake_data_file) 15 | 16 | # Split the data into training and testing sets 17 | X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) 18 | 19 | # Create and train the Random Forest classifier 20 | clf = RandomForestClassifier() 21 | clf.fit(X_train, y_train) 22 | 23 | # Make predictions on the test set 24 | predictions = clf.predict(X_test) 25 | 26 | calculate_accuracy(y_test, predictions) 27 | 28 | model_filename = 'samples/random_forest_model.joblib' 29 | joblib.dump(clf, model_filename) 30 | 31 | if __name__ == '__main__': 32 | main() -------------------------------------------------------------------------------- /src/samples/lightgbm_boostingtree.joblib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIMIC-LOGICS/Akamai-Mouse-Protection/50d279b3bf7b747a6d9061e4670377b878470602/src/samples/lightgbm_boostingtree.joblib -------------------------------------------------------------------------------- /src/samples/lightgbm_boostingtree_custom.joblib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIMIC-LOGICS/Akamai-Mouse-Protection/50d279b3bf7b747a6d9061e4670377b878470602/src/samples/lightgbm_boostingtree_custom.joblib -------------------------------------------------------------------------------- /src/samples/random_forest_model.joblib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MIMIC-LOGICS/Akamai-Mouse-Protection/50d279b3bf7b747a6d9061e4670377b878470602/src/samples/random_forest_model.joblib --------------------------------------------------------------------------------