diff --git a/.gitignore b/.gitignore index a348ffd..50b33e5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -aaaastart.sh +start.sh config.toml -__pycache__ \ No newline at end of file +__pycache__ diff --git a/README.md b/README.md index ab1848a..61c2523 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,32 @@ -# RADAR +# Novel, Anti-Abuse -RADAR is a watchdog software to watch file modifications, deletions, movements, creations and scan such files with YARA rules and later analyse them with AI (configurable) and send to your discord webhook (configurable)! +Introducing Anti-Abuse by Novel. -# Install +Anti-Abuse is an ✨ FREE, Open-Sourced radar based on yara rules built for pterodactyl nodes. -Firstly ensecure you have python3 and pip installed. +## Features +1. Watchdog based real-time monitoring. +2. Easily customizable by [Yara Rule](https://yara.readthedocs.io/en/stable/writingrules.html). +3. Various Integrations(discord webhook, etc). +4. Easy re-check action through AI-Based Analysis. -Secondly install packages we are using for RADAR: +## Installation -```python +Requirements: python, keyboard, brain + +1. Install requirements +```bash pip install watchdog tomllib yara ``` +2. Configure your config.toml and yara rules Thirdly run configure config.toml, upload your YARA (.yar and .yara) signatures in /signatures and then finally run RADAR! ```python python3 main.py ``` -Done! You're running RADAR watchdog. +Done! You're now running Anti-Abuse. # Tips @@ -27,6 +35,11 @@ Tip 1: You don't know how to write YARA rules? # Reporting security issue or vulnerability -Please contact us directly per email `lisahonkay@gmail.com` or using duscord `@_lisa_ns_` or `@inxtagram` to report security issue or vulnerability! +Please contact us on email: -Made with <3 in python by inxtagram and _lisa_ns_, licensed under [GNU GENERAL PUBLIC LICENSE, Version 3](http://lhhomeserver.ddns.net:3000/Lisa_Stuff/RADAR/src/branch/main/LICENSE) \ No newline at end of file +|Maintainer|Contact| +|----|---| +|Lisa|lisahonkay@gmail.com, `@_lisa_ns_` on discord| +|Lin|contact@who.ad, @inxtagram` on discord| + +Made with ❤️ by inxtagram and `_lisa_ns_`, licensed under [GNU GENERAL PUBLIC LICENSE, Version 3](http://lhhomeserver.ddns.net:3000/Lisa_Stuff/RADAR/src/branch/main/LICENSE) \ No newline at end of file diff --git a/TODO.md b/TODO.md index 3320a35..259751c 100644 --- a/TODO.md +++ b/TODO.md @@ -4,8 +4,9 @@ TODO: 2. Integration with pterodactyl (ref. py-dactyl or https://dashflo.net/docs/api/pterodactyl/v1/) 3. Integration with pelican (ref? https://pelican.dev/) 4. Integration with docker -5. Several AI Models support, if one fails to respond another model from the list will be used. Example: models = ["model1","model2","model3","etc"] 6. Multi threading support (for scans) -7. Multiple pathes support. Example: watchdog_path = ["./path/one","/root/test/","./etc"] -8. Includes ability to add ignore path in integrations or use path in integration , of course with multiple pathes support too -9. Ability to add ignore path or ignore file (multiple support too!) \ No newline at end of file +9. Ability to add ignore path or ignore file (multiple support too!) + +~~7. Multiple pathes support. Example: watchdog_path = ["./path/one","/root/test/","./etc"]~~ +~~5. Several AI Models support, if one fails to respond another model from the list will be used. Example: models = ["model1","model2","model3","etc"]~~ +~~8. Includes ability to add ignore path in integrations or use path in integration , of course with multiple pathes support too~~ diff --git a/aaaastart.sh b/aaaastart.sh new file mode 100644 index 0000000..65f5bb0 --- /dev/null +++ b/aaaastart.sh @@ -0,0 +1 @@ +ss \ No newline at end of file diff --git a/config.toml b/config.toml index c447e20..22850d3 100644 --- a/config.toml +++ b/config.toml @@ -1,4 +1,6 @@ + ver = "250325d" +machineID = "node1" #*************************************************# # # @@ -8,7 +10,7 @@ ver = "250325d" [LANGUGAE.english] -radarStarted = "RADAR Started with in - {}s." +novelStarted = "Novel(Anti Abuse) Started within - {}s." #**************************************************# @@ -33,7 +35,7 @@ watchdogPath = "./" SignaturePath = "./signatures" watchdogIgnorePath = ["./signatures"] -watchdogIgnoreFile = ["main.py"] +watchdogIgnoreFile = ["./main.py", "./config.toml", "es/common.yara"] #**************************************************# # # @@ -44,16 +46,16 @@ watchdogIgnoreFile = ["main.py"] [INTEGRATION.AI] enabled = true -generate_model = "llama-3.1-8b-instant" # for home usage gemma3:1b recommended, for Groq llama-3.1-8b-instant +generate_models = ["llama-3.2-90b-vision-preview","llama-3.3-70b-versatile","llama-3.3-70b-specdec","llama-3.2-11b-vision-preview","llama3-70b-8192","llama-3.1-8b-instant","llama3-8b-8192","llama-3.2-3b-preview","llama-3.2-1b-preview"] # for home usage gemma3:1b recommended, for Groq llama-3.1-8b-instant generate_endpoint = "http://IP:PORT/api/generate" # Can be empty if using groq use_groq = true -groq_api_token = "" # Get one at https://console.groq.com/keys +groq_api_token = "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" # Get one at https://console.groq.com/keys -prompt = "Analyze the given code and return an abuse score (0-10) with a brief reason. Example abuses: Crypto Miner, Shell Access, Nezha Proxy (VPN/Proxy usage), Disk Filling, Tor, DDoS, Abusive Resource Usage. Response format: '**5/10** '. No extra messages." +prompt = "Analyze the given code and return an abuse score (0-10) with a brief reason. Example abuses: Crypto Mining, Shell Access, Nezha Proxy (VPN/Proxy usage), Disk Filling, Tor, DDoS, Abusive Resource Usage. Response format: '**5/10** '. No extra messages." [INTEGRATION.DISCORD] enabled = true -webhook_url = "" +webhook_url = "https://discord.com/api/webhooks/1353420407511973948/knrSGrfLDvi_60Mese1LAIBmkrK05a_L4PmyyE7R7wvGZEXiWdzrRT8pdicj0aHe88m4" truncate_text = true # Used only if AI INTEGRATION is enabled, trunclates text if true to maxium allowed characters or when false splits in few webhook messages. diff --git a/main.py b/main.py index 3f29976..ebd09ca 100644 --- a/main.py +++ b/main.py @@ -3,22 +3,32 @@ import time, os, tomllib from utils.Logger import Log from utils.WatchdogHandler import DirWatcher -#endregion +#endregion #region Initialize t = time.time() with open("config.toml", "rb") as f: data = tomllib.load(f) +Log.v(str(data)) path = data['DETECTION']['watchdogPath'] Log.v(""" - ____ ____ - / __ \\____ _/ __ \\____ ______ - / /_/ / __ `/ / / / __ `/ ___/ - / _, _/ /_/ / /_/ / /_/ / / -/_/ |_|\\__,_/_____/\\__,_/_/ (ver. {}) + + o o 8 + 8b 8 8 + 8`b 8 .oPYo. o o .oPYo. 8 + 8 `b 8 8 8 Y. .P 8oooo8 8 + 8 `b8 8 8 `b..d' 8. 8 + 8 `8 `YooP' `YP' `Yooo' 8 + ..:::..:.....:::...:::.....:.. + :::::::::::::::::::::::::::::: + + Product - ANTI-ABUSE + Release - {} + License - GNU GENERAL PUBLIC LICENSE, Version 3 + """.format(data['ver'])) #endregion @@ -26,7 +36,7 @@ if __name__ == "__main__": with DirWatcher(path, interval=1) as watcher: watcher.run() - Log.s(data['LANGUGAE']['english']['radarStarted'].format(str(round(time.time() - t, 1)))) + Log.s(data['LANGUGAE']['english']['novelStarted'].format(str(round(time.time() - t, 1)))) try: while True: time.sleep(1) diff --git a/signatures/common.yara b/signatures/common.yara index d175618..9b51fb5 100644 --- a/signatures/common.yara +++ b/signatures/common.yara @@ -1,51 +1,56 @@ - -rule CHIENESE_NEZHA_ARGO -{ - strings: - $a1 = "TkVaSEE=" - $a2 = "tunnel.json" - $a3 = "vless" - $a4 = "dmxlc3M=" - $a5 = "/vmess" - $a6 = "L3ZtZXNz" - $a7 = "V0FSUA==" - $a8 = "/eooce/" - $a9 = "ARGO_AUTH" - $a10 = "--edge-ip-version" - $a11 = "LS1lZGdlLWlwLXZlcnNpb24=" - $12 = "sub.txt" - $13 = "Server\x20is\x20running\x20on\x20port\x20" - $14 = "nysteria2" - $15 = "openssl req" - +rule CHINESE_NEZHA_ARGO { + strings: + $a1 = "TkVaSEE=" // Base64 for "NEZHA" + $a2 = "tunnel.json" + $a3 = "vless" + $a4 = "dmxlc3M=" // Base64 for "vless" + $a5 = "/vmess" + $a6 = "L3ZtZXNz" // Base64 for "/vmess" + $a7 = "V0FSUA==" // Base64 for "WARP" + $a8 = "/eooce/" + $a9 = "ARGO_AUTH" + $a10 = "--edge-ip-version" + $a11 = "LS1lZGdlLWlwLXZlcnNpb24=" // Base64 for "--edge-ip-version" + $a12 = "sub.txt" + $a13 = "Server\x20is\x20running\x20on\x20port\x20" + $a14 = "nysteria2" + $a15 = "openssl req" condition: - 2 of ($a1, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $a10, $a11, $12, $13, $14, $15) + 2 of ($a*) } -rule OBFSCATED_CODE -{ +rule OBFUSCATED_CODE { meta: description = "Detects an obfuscated script" - strings: - $f1 = "_0x" nocase - $f2 = "\x20" nocase - $f3 = "\x0a" nocase - $f5 = "openssl req -new -x509" nocase - $f6 = "cert.pem" nocase - $f7 = "private.key" nocase + $f1 = "0x" nocase + $f2 = "x20" nocase + $f3 = "x0a" nocase condition: - 2 of ($f*) + 2 of ($f1, $f2, $f3) } - -rule OVERLOAD_CRYPTO_MINER -{ - meta: +rule OVERLOAD_CRYPTO_MINER { + meta: ref = "https://gist.github.com/GelosSnake/c2d4d6ef6f93ccb7d3afb5b1e26c7b4e" - strings: - $a1 = "stratum+tcp" + strings: + $a1 = "stratum+tcp" + $a2 = "xmrig" + $a3 = "crypto" + condition: - $a1 + any of them } +rule REVERSE_SHELL { + strings: + $a1 = "0>&1" + $a2 = "sh" + $a3 = "-i" + $a4 = "0<&196" + $a5 = "<>/dev/tcp" + $a6 = "socket.socket" + + condition: + 2 of them +} \ No newline at end of file diff --git a/utils/Scanner.py b/utils/Scanner.py index 3f8d10b..91ca5ca 100644 --- a/utils/Scanner.py +++ b/utils/Scanner.py @@ -1,7 +1,6 @@ #region Imports -import os -import yara -import tomllib +import os, yara, tomllib +from utils.Logger import Log #endregion #region Variables @@ -29,17 +28,17 @@ def scan(src): error_messages = {} for filename in os.listdir(data['DETECTION']['SignaturePath']): - if filename.endswith((".yara")): + if filename.endswith(".yara") or filename.endswith(".yar"): # both are yara extensions ok rule_path = os.path.join(data['DETECTION']['SignaturePath'], filename) try: rules = yara.compile(filepath=rule_path) file_matches = rules.match(data=src) if file_matches: matches[filename] = file_matches - # for match in file_matches: - # print(f" - Rule: {match.rule}") + #for match in file_matches: + # Log.v(f" - Rule: {match.rule}") except yara.Error as e: + Log.e(e) error_messages[filename] = e - return matches, error_messages #endregion \ No newline at end of file diff --git a/utils/WatchdogHandler.py b/utils/WatchdogHandler.py index 0b63102..0f82442 100644 --- a/utils/WatchdogHandler.py +++ b/utils/WatchdogHandler.py @@ -1,14 +1,18 @@ -"""Context manager for basic directory watching. +""" +CREDIT -Includes a workaround for . +Context manager for basic directory watching. + - . """ from datetime import datetime, timedelta from pathlib import Path from time import sleep +import threading +import time from typing import Callable, Self from utils.Logger import Log -import tomllib, time +import tomllib from watchdog.events import FileSystemEvent, FileSystemEventHandler from watchdog.observers import Observer @@ -29,17 +33,35 @@ if not isinstance(paths, list): ignore_paths = data['DETECTION'].get('watchdogIgnorePath', []) ignore_files = data['DETECTION'].get('watchdogIgnoreFile', []) + def s(input_dict): return [ {"name": key, "value": '\n'.join(' - ' + str(item) for item in items)} for key, items in input_dict.items() ] + + def c(d): - c=0 + count = 0 for key in d: if isinstance(d[key], list): - c += len(d[key]) - return c + count += len(d[key]) + return count + + +def analysis(event_path: str, file_content: str, flag_type: str): + """ + Process file events in a separate thread. + This function scans the file content, and if flagged, + performs AI analysis and sends a webhook notification. + """ + results = scan(file_content) + if results[0]: + Log.s(f"Flagged {event_path}") + analysis = ai_analyse(file_content) + msg = f"Total Flagged Pattern: {str(c(results[0]))}\n\n{analysis}" + webhook(event_path, s(results[0]), msg) + class DirWatcher: """Run a function when a directory changes.""" @@ -49,18 +71,18 @@ class DirWatcher: def __init__( self, watch_dir: Path, - interval: int = 0.2, - cooldown: int = 0.1, + interval: float = 0.2, + cooldown: float = 0.1, ): if interval < self.min_cooldown: raise ValueError( - f"Interval of {interval} seconds is less than the minimum cooldown of" - f" {self.min_cooldown} seconds." + f"Interval of {interval} seconds is less than the minimum cooldown of " + f"{self.min_cooldown} seconds." ) if cooldown < self.min_cooldown: raise ValueError( - f"Cooldown of {cooldown} seconds is less than the minimum cooldown of" - f" {self.min_cooldown} seconds." + f"Cooldown of {cooldown} seconds is less than the minimum cooldown of " + f"{self.min_cooldown} seconds." ) self.watch_dir = watch_dir self.interval = interval @@ -72,7 +94,7 @@ class DirWatcher: ModifiedFileHandler(scan, self.cooldown), self.watch_dir, recursive=True ) - Log.s(data['LANGUGAE']['english']['radarStarted'].format(str(round(time.time() - t, 5)))) + Log.s(data['LANGUGAE']['english']['novelStarted'].format(str(round(time.time() - t, 5)))) self.observer.start() return self @@ -99,71 +121,78 @@ class DirWatcher: class ModifiedFileHandler(FileSystemEventHandler): - """Handle modified files.""" + """Handle modified files using threading for processing.""" - def __init__(self, func: Callable[[FileSystemEvent], None], cooldown: int): + def __init__(self, func: Callable[[FileSystemEvent], None], cooldown: float): self.cooldown = timedelta(seconds=cooldown) self.triggered_time = datetime.min - def on_any_event(self, event): + def ignore_event(self, event: FileSystemEvent) -> bool: for ignore_path in ignore_paths: if event.src_path.startswith(ignore_path): return True for ignore_file in ignore_files: if event.src_path.endswith(ignore_file): return True - if(event.src_path == "."): + if event.src_path == ".": + return True + return False + + def on_any_event(self, event: FileSystemEvent): + if self.ignore_event(event): return True - def on_modified(self, event: FileSystemEvent): - try: - if (datetime.now() - self.triggered_time) > self.cooldown: - src = open(event.src_path, "r").read() - if(event.src_path == "."): - return + if self.ignore_event(event): + return + if (datetime.now() - self.triggered_time) > self.cooldown: + try: + with open(event.src_path, "r") as f: + src = f.read() Log.v(f"FILE MODF | {event.src_path}") - r = scan(src) - if r[0]: - Log.s(f"Flagged {event.src_path}") - analyse = ai_analyse(src) - webhook(event.src_path, s(r[0]), f"Total Flagged Pattern: {str(c(r[0]))}\n\n{analyse}") + # Process in a separate thread + threading.Thread(target=analysis, args=(event.src_path, src, "modification")).start() self.triggered_time = datetime.now() - except: pass + except Exception: + pass def on_moved(self, event: FileSystemEvent): - - try: - if (datetime.now() - self.triggered_time) > self.cooldown: + if self.ignore_event(event): + return + if (datetime.now() - self.triggered_time) > self.cooldown: + try: Log.v(f"FILE MOV | {event.src_path} > {event.dest_path}") - r = scan(event.src_path) - if r[0]: - Log.s(f"Flagged {event.src_path}") - analyse = ai_analyse(event.src_path) - webhook(event.src_path, s(r[0]), f"Total Flagged Pattern: {str(c(r[0]))}\n\n{analyse}") + # For moved events, you might choose to scan the original or destination file. + # Here, we'll scan the source path. + with open(event.src_path, "r") as f: + src = f.read() + threading.Thread(target=analysis, args=(event.src_path, src, "moved")).start() self.triggered_time = datetime.now() - except: pass + except Exception: + pass def on_deleted(self, event: FileSystemEvent): - try: - if (datetime.now() - self.triggered_time) > self.cooldown: + if self.ignore_event(event): + return + if (datetime.now() - self.triggered_time) > self.cooldown: + try: Log.v(f"FILE DEL | {event.src_path}") self.triggered_time = datetime.now() - except: pass + except Exception: + pass def on_created(self, event: FileSystemEvent): - try: - print(1) - if (datetime.now() - self.triggered_time) > self.cooldown: + if self.ignore_event(event): + return + if (datetime.now() - self.triggered_time) > self.cooldown: + try: if event.is_directory: - return None - else: + return + else: Log.v(f"file created: {event.src_path}") - r = scan(event.src_path) - if r[0]: - Log.s(f"Flagged {event.src_path}") - analyse = ai_analyse(event.src_path) - webhook(event.src_path, s(r[0]), f"Total Flagged Pattern: {str(c(r[0]))}\n\n{analyse}") - - self.triggered_time = datetime.now() - except: pass \ No newline at end of file + with open(event.src_path, "r") as f: + content = f.read() + threading.Thread(target=analysis, args=(event.src_path, content, "creation")).start() + self.triggered_time = datetime.now() + except Exception: + pass diff --git a/utils/integration/AI.py b/utils/integration/AI.py index 829d0ec..0773c4d 100644 --- a/utils/integration/AI.py +++ b/utils/integration/AI.py @@ -11,7 +11,7 @@ with open("./config.toml", "rb") as f: enabled = data["INTEGRATION"]["AI"]["enabled"] generate_endpoint = data["INTEGRATION"]["AI"]["generate_endpoint"] -generate_model = data["INTEGRATION"]["AI"]["generate_model"] +model_list = data["INTEGRATION"]["AI"]["generate_models"] use_groq = data["INTEGRATION"]["AI"]["use_groq"] groq_api = data["INTEGRATION"]["AI"]["groq_api_token"] prompt = data["INTEGRATION"]["AI"]["prompt"] @@ -22,52 +22,56 @@ if use_groq: #endregion def generate_response(data): - """Generate a response using the Groq API.""" - try: - # Create headers - headers = { - "Content-Type": "application/json", - } + """Generate a response using the Groq or OLLAMA API.""" + error_messages = [] + for generate_model in model_list: + try: + headers = { + "Content-Type": "application/json", + } - # Add authorization header if using Groq - if use_groq: - headers["Authorization"] = f"Bearer {groq_api}" + # Add authorization header if using Groq + if use_groq: + headers["Authorization"] = f"Bearer {groq_api}" - # Create payload - payload = { - "model": generate_model, - "temperature": 1, - "max_completion_tokens": 1024, - "top_p": 1, - "stream": False, - "stop": None, - } + # Create payload + payload = { + "model": generate_model, + "temperature": 1, + "max_completion_tokens": 1024, + "top_p": 1, + "stream": False, + "stop": None, + } - # Conditional message structure for Groq - if use_groq: - payload["messages"] = [ - { - "role": "system", - "content": f"{prompt}" - }, - { - "role": "user", - "content": f"```code\n{data}\n```" - } - ] - else: - payload["prompt"] = f"Using this data: {data}. Respond to this prompt: {prompt}" + # Conditional message structure for Groq + if use_groq: + payload["messages"] = [ + { + "role": "system", + "content": f"{prompt}" + }, + { + "role": "user", + "content": f"```code\n{data}\n```" + } + ] + else: + payload["prompt"] = f"Using this data: {data}. Respond to this prompt: {prompt}\n" - response = requests.post(generate_endpoint, json=payload, headers=headers) - response.raise_for_status() - if use_groq: - return response.json()["choices"][0]["message"]["content"] - else: - return response.json() + response = requests.post(generate_endpoint, json=payload, headers=headers) + response.raise_for_status() + if use_groq: + return response.json()["choices"][0]["message"]["content"] + else: + return response.json() - except requests.exceptions.RequestException as e: - Log.e(f"Failed to generate response: {e}") - return None + except requests.exceptions.RequestException as e: + Log.e(f"Failed to generate response: {e}") + Log.e(f"Using model: {generate_model}") + error_messages.append(f"Model {generate_model} failed: {e}") + return None + return f"All models failed. Errors: {error_messages}" def ai_analyse(src): @@ -81,9 +85,9 @@ def ai_analyse(src): #Log.s(f"Generated Response: {response}") return response else: - Log.e("AI did not respond.") + return "No AI Description provided for this action; check config.toml maybe?" except Exception as e: Log.e(f"Unexpected error: {e}") else: - return "AI integration is disabled in the configuration, enable AI integration for AI File Analyse." + return "No AI Description provided for this action; check config.toml maybe?" return None diff --git a/utils/integration/Discord.py b/utils/integration/Discord.py index 86f8323..0492b97 100644 --- a/utils/integration/Discord.py +++ b/utils/integration/Discord.py @@ -59,7 +59,7 @@ def webhook(file_path, yara_matches, ai=""): if truncate_text_flag: # Single embed if truncated embeds.append({ - "title": "⚠️ WATCHDOG ALERT ⚠️", + "title": f"⚠️ WATCHDOG ALERT ⚠️ - {config_data['machineID']}", "description": description, "color": 65280, "fields": yara_matches,