77import contextlib
88import logging
99import random
10+ import time
11+ import math
1012import re
1113import traceback
1214from ctypes import POINTER , WINFUNCTYPE , byref , c_bool , c_int , create_unicode_buffer , memmove , sizeof , wintypes
@@ -280,6 +282,53 @@ def interact_with_window(hwnd, lparam):
280282 return True
281283
282284
285+ def move_mouse_realistically (dest_x , dest_y ):
286+ current_pos = get_cursor_position ()
287+ start_x , start_y = current_pos ['x' ], current_pos ['y' ]
288+
289+ # Calculate distance to determine total duration
290+ distance = math .sqrt ((dest_x - start_x )** 2 + (dest_y - start_y )** 2 )
291+ if distance < 1 :
292+ return
293+
294+ # 1. Create random control points for a natural curve
295+ # These points 'pull' the path away from a perfect straight line
296+ # Offsetting these creates a unique arc every time
297+ offset = distance * 0.15
298+ ctrl_x1 = start_x + (dest_x - start_x ) * 0.3 + random .uniform (- offset , offset )
299+ ctrl_y1 = start_y + (dest_y - start_y ) * 0.3 + random .uniform (- offset , offset )
300+ ctrl_x2 = start_x + (dest_x - start_x ) * 0.7 + random .uniform (- offset , offset )
301+ ctrl_y2 = start_y + (dest_y - start_y ) * 0.7 + random .uniform (- offset , offset )
302+
303+ steps = random .randint (30 , 50 ) if distance < 500 else random .randint (60 , 90 )
304+
305+ for i in range (steps + 1 ):
306+ t = i / steps
307+
308+ # Cubic Bezier Formula for smooth pathing
309+ curr_x = int ((1 - t )** 3 * start_x + 3 * (1 - t )** 2 * t * ctrl_x1 +
310+ 3 * (1 - t ) * t ** 2 * ctrl_x2 + t ** 3 * dest_x )
311+ curr_y = int ((1 - t )** 3 * start_y + 3 * (1 - t )** 2 * t * ctrl_y1 +
312+ 3 * (1 - t ) * t ** 2 * ctrl_y2 + t ** 3 * dest_y )
313+
314+ USER32 .SetCursorPos (curr_x , curr_y )
315+
316+ # 3. Speed Randomization (The "Human" Factor)
317+ # Base sleep + Sinusoidal deceleration + Gaussian noise (jitter)
318+ base_sleep = 0.001
319+ deceleration = (math .sin (t * math .pi ) * 0.005 ) # Slower at start/end
320+ jitter = random .uniform (0 , 0.003 ) # Tiny random pauses
321+
322+ # Occasional "Micro-hesitation" (1% chance to pause for a frame)
323+ if random .random () < 0.01 :
324+ time .sleep (random .uniform (0.01 , 0.03 ))
325+
326+ time .sleep (base_sleep + deceleration + jitter )
327+
328+ # Final snap to ensure precision
329+ USER32 .SetCursorPos (dest_x , dest_y )
330+
331+
283332# Callback procedure invoked for every enumerated window.
284333def handle_window_interaction (hwnd , lparam ):
285334 # we also want to inspect the "parent" windows, not just the children
@@ -295,7 +344,7 @@ def get_window_list(hwnd, lparam):
295344 return True
296345
297346
298- def move_mouse ():
347+ def move_mouse_random ():
299348 # To avoid mousing over desktop icons, use 1/4 of the total resolution as the starting pixel
300349 x = random .randint (RESOLUTION_WITHOUT_TASKBAR ["x" ] // 4 , RESOLUTION_WITHOUT_TASKBAR ["x" ])
301350 y = random .randint (0 , RESOLUTION_WITHOUT_TASKBAR ["y" ])
@@ -306,7 +355,7 @@ def move_mouse():
306355 # the mouse events. This actually moves the cursor around which might
307356 # cause some unintended activity on the desktop. We might want to make
308357 # this featur optional.
309- USER32 . SetCursorPos (x , y )
358+ move_mouse_realistically (x , y )
310359
311360
312361def click_mouse ():
@@ -320,7 +369,7 @@ def click_mouse():
320369def click_around (hwnd , workable_range_x , workable_range_y ):
321370 USER32 .SetForegroundWindow (hwnd )
322371 # first click the middle
323- USER32 . SetCursorPos (RESOLUTION ["x" ] // 2 , RESOLUTION ["y" ] // 2 )
372+ move_mouse_realistically (RESOLUTION ["x" ] // 2 , RESOLUTION ["y" ] // 2 )
324373 click_mouse ()
325374 KERNEL32 .Sleep (50 )
326375 click_mouse ()
@@ -332,23 +381,23 @@ def click_around(hwnd, workable_range_x, workable_range_y):
332381 # make sure the window still exists
333382 if USER32 .IsWindowVisible (hwnd ):
334383 USER32 .SetForegroundWindow (hwnd )
335- USER32 . SetCursorPos (x , RESOLUTION ["y" ] // 2 )
384+ move_mouse_realistically (x , RESOLUTION ["y" ] // 2 )
336385 click_mouse ()
337386 KERNEL32 .Sleep (50 )
338387 click_mouse ()
339388 KERNEL32 .Sleep (50 )
340389 if not USER32 .IsWindowVisible (hwnd ):
341390 break
342391 USER32 .SetForegroundWindow (hwnd )
343- USER32 . SetCursorPos (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
392+ move_mouse_realistically (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
344393 click_mouse ()
345394 KERNEL32 .Sleep (50 )
346395 click_mouse ()
347396 KERNEL32 .Sleep (50 )
348397 if not USER32 .IsWindowVisible (hwnd ):
349398 break
350399 USER32 .SetForegroundWindow (hwnd )
351- USER32 . SetCursorPos (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
400+ move_mouse_realistically (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
352401 click_mouse ()
353402 KERNEL32 .Sleep (50 )
354403 click_mouse ()
@@ -368,7 +417,7 @@ def click_around(hwnd, workable_range_x, workable_range_y):
368417 else :
369418 point = re .match (CURSOR_POSITION_REGEX , instruction )
370419 if point and len (point .regs ) == 3 :
371- USER32 . SetCursorPos (int (point .group (1 )), int (point .group (2 )))
420+ move_mouse_realistically (int (point .group (1 )), int (point .group (2 )))
372421 KERNEL32 .Sleep (50 )
373422 else :
374423 log .info ("Breaking out of document window click loop as our window went away" )
@@ -459,7 +508,7 @@ def realistic_human_cursor_movement():
459508 counter += RESOLUTION_WITHOUT_TASKBAR ["x" ] // 64
460509 x = floor (max (0 , min (start_x + counter + + fuzzy_x , RESOLUTION_WITHOUT_TASKBAR ["x" ])))
461510 y = floor (start_y )
462- USER32 . SetCursorPos (x , y )
511+ move_mouse_realistically (x , y )
463512 KERNEL32 .Sleep (50 )
464513
465514
@@ -554,7 +603,7 @@ def run(self):
554603 return
555604 match = re .match (CURSOR_POSITION_REGEX , instruction , flags = re .IGNORECASE )
556605 if match and len (match .regs ) == 3 :
557- USER32 . SetCursorPos (int (match .group (1 )), int (match .group (2 )))
606+ move_mouse_realistically (int (match .group (1 )), int (match .group (2 )))
558607 KERNEL32 .Sleep (interval )
559608 continue
560609 match = re .match (WAIT_REGEX , instruction , flags = re .IGNORECASE )
@@ -576,24 +625,24 @@ def run(self):
576625 rng = random .randint (0 , 7 )
577626 if rng > 1 : # 0-1
578627 if rng < 4 : # 2-3 25% of the time move the cursor on the middle of the screen for x and move around
579- USER32 . SetCursorPos (RESOLUTION ["x" ] // 2 , 0 )
628+ move_mouse_realistically (RESOLUTION ["x" ] // 2 , 0 )
580629 # Avoid clicking on console windows and suspending execution
581630 if not cursor_over_console_window ():
582631 click_mouse ()
583- move_mouse ()
632+ move_mouse_random ()
584633 elif (
585634 rng >= 6
586635 ): # 6-7 25% of the time do realistic human movements for things like https://thehackernews.com/2023/11/lummac2-malware-deploys-new.html
587636 realistic_human_cursor_movement ()
588637 else : # 4-5 25% of the time move the cursor somewhere random and click/move around
589- USER32 . SetCursorPos (
638+ move_mouse_realistically (
590639 int (RESOLUTION_WITHOUT_TASKBAR ["x" ] / random .uniform (1 , 16 )),
591640 int (RESOLUTION_WITHOUT_TASKBAR ["y" ] / random .uniform (1 , 16 )),
592641 )
593642 # Avoid clicking on console windows and suspending execution
594643 if not cursor_over_console_window ():
595644 click_mouse ()
596- move_mouse ()
645+ move_mouse_random ()
597646
598647 if (seconds % (15 + randoff )) == 0 :
599648 # curwind = USER32.GetForegroundWindow()
0 commit comments