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,51 @@ 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+
292+ # 1. Create random control points for a natural curve
293+ # These points 'pull' the path away from a perfect straight line
294+ # Offsetting these creates a unique arc every time
295+ offset = distance * 0.15
296+ ctrl_x1 = start_x + (dest_x - start_x ) * 0.3 + random .uniform (- offset , offset )
297+ ctrl_y1 = start_y + (dest_y - start_y ) * 0.3 + random .uniform (- offset , offset )
298+ ctrl_x2 = start_x + (dest_x - start_x ) * 0.7 + random .uniform (- offset , offset )
299+ ctrl_y2 = start_y + (dest_y - start_y ) * 0.7 + random .uniform (- offset , offset )
300+
301+ steps = random .randint (30 , 50 ) if distance < 500 else random .randint (60 , 90 )
302+
303+ for i in range (steps + 1 ):
304+ t = i / steps
305+
306+ # Cubic Bezier Formula for smooth pathing
307+ curr_x = int ((1 - t )** 3 * start_x + 3 * (1 - t )** 2 * t * ctrl_x1 +
308+ 3 * (1 - t ) * t ** 2 * ctrl_x2 + t ** 3 * dest_x )
309+ curr_y = int ((1 - t )** 3 * start_y + 3 * (1 - t )** 2 * t * ctrl_y1 +
310+ 3 * (1 - t ) * t ** 2 * ctrl_y2 + t ** 3 * dest_y )
311+
312+ USER32 .SetCursorPos (curr_x , curr_y )
313+
314+ # 3. Speed Randomization (The "Human" Factor)
315+ # Base sleep + Sinusoidal deceleration + Gaussian noise (jitter)
316+ base_sleep = 0.001
317+ deceleration = (math .sin (t * math .pi ) * 0.005 ) # Slower at start/end
318+ jitter = random .uniform (0 , 0.003 ) # Tiny random pauses
319+
320+ # Occasional "Micro-hesitation" (1% chance to pause for a frame)
321+ if random .random () < 0.01 :
322+ time .sleep (random .uniform (0.01 , 0.03 ))
323+
324+ time .sleep (base_sleep + deceleration + jitter )
325+
326+ # Final snap to ensure precision
327+ USER32 .SetCursorPos (dest_x , dest_y )
328+
329+
283330# Callback procedure invoked for every enumerated window.
284331def handle_window_interaction (hwnd , lparam ):
285332 # we also want to inspect the "parent" windows, not just the children
@@ -295,7 +342,7 @@ def get_window_list(hwnd, lparam):
295342 return True
296343
297344
298- def move_mouse ():
345+ def move_mouse_random ():
299346 # To avoid mousing over desktop icons, use 1/4 of the total resolution as the starting pixel
300347 x = random .randint (RESOLUTION_WITHOUT_TASKBAR ["x" ] // 4 , RESOLUTION_WITHOUT_TASKBAR ["x" ])
301348 y = random .randint (0 , RESOLUTION_WITHOUT_TASKBAR ["y" ])
@@ -306,7 +353,7 @@ def move_mouse():
306353 # the mouse events. This actually moves the cursor around which might
307354 # cause some unintended activity on the desktop. We might want to make
308355 # this featur optional.
309- USER32 . SetCursorPos (x , y )
356+ move_mouse_realistically (x , y )
310357
311358
312359def click_mouse ():
@@ -320,7 +367,7 @@ def click_mouse():
320367def click_around (hwnd , workable_range_x , workable_range_y ):
321368 USER32 .SetForegroundWindow (hwnd )
322369 # first click the middle
323- USER32 . SetCursorPos (RESOLUTION ["x" ] // 2 , RESOLUTION ["y" ] // 2 )
370+ move_mouse_realistically (RESOLUTION ["x" ] // 2 , RESOLUTION ["y" ] // 2 )
324371 click_mouse ()
325372 KERNEL32 .Sleep (50 )
326373 click_mouse ()
@@ -332,23 +379,23 @@ def click_around(hwnd, workable_range_x, workable_range_y):
332379 # make sure the window still exists
333380 if USER32 .IsWindowVisible (hwnd ):
334381 USER32 .SetForegroundWindow (hwnd )
335- USER32 . SetCursorPos (x , RESOLUTION ["y" ] // 2 )
382+ move_mouse_realistically (x , RESOLUTION ["y" ] // 2 )
336383 click_mouse ()
337384 KERNEL32 .Sleep (50 )
338385 click_mouse ()
339386 KERNEL32 .Sleep (50 )
340387 if not USER32 .IsWindowVisible (hwnd ):
341388 break
342389 USER32 .SetForegroundWindow (hwnd )
343- USER32 . SetCursorPos (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
390+ move_mouse_realistically (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
344391 click_mouse ()
345392 KERNEL32 .Sleep (50 )
346393 click_mouse ()
347394 KERNEL32 .Sleep (50 )
348395 if not USER32 .IsWindowVisible (hwnd ):
349396 break
350397 USER32 .SetForegroundWindow (hwnd )
351- USER32 . SetCursorPos (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
398+ move_mouse_realistically (x , random .randint (workable_range_y [0 ], workable_range_y [1 ]))
352399 click_mouse ()
353400 KERNEL32 .Sleep (50 )
354401 click_mouse ()
@@ -368,7 +415,7 @@ def click_around(hwnd, workable_range_x, workable_range_y):
368415 else :
369416 point = re .match (CURSOR_POSITION_REGEX , instruction )
370417 if point and len (point .regs ) == 3 :
371- USER32 . SetCursorPos (int (point .group (1 )), int (point .group (2 )))
418+ move_mouse_realistically (int (point .group (1 )), int (point .group (2 )))
372419 KERNEL32 .Sleep (50 )
373420 else :
374421 log .info ("Breaking out of document window click loop as our window went away" )
@@ -459,7 +506,7 @@ def realistic_human_cursor_movement():
459506 counter += RESOLUTION_WITHOUT_TASKBAR ["x" ] // 64
460507 x = floor (max (0 , min (start_x + counter + + fuzzy_x , RESOLUTION_WITHOUT_TASKBAR ["x" ])))
461508 y = floor (start_y )
462- USER32 . SetCursorPos (x , y )
509+ move_mouse_realistically (x , y )
463510 KERNEL32 .Sleep (50 )
464511
465512
@@ -554,7 +601,7 @@ def run(self):
554601 return
555602 match = re .match (CURSOR_POSITION_REGEX , instruction , flags = re .IGNORECASE )
556603 if match and len (match .regs ) == 3 :
557- USER32 . SetCursorPos (int (match .group (1 )), int (match .group (2 )))
604+ move_mouse_realistically (int (match .group (1 )), int (match .group (2 )))
558605 KERNEL32 .Sleep (interval )
559606 continue
560607 match = re .match (WAIT_REGEX , instruction , flags = re .IGNORECASE )
@@ -576,24 +623,24 @@ def run(self):
576623 rng = random .randint (0 , 7 )
577624 if rng > 1 : # 0-1
578625 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 )
626+ move_mouse_realistically (RESOLUTION ["x" ] // 2 , 0 )
580627 # Avoid clicking on console windows and suspending execution
581628 if not cursor_over_console_window ():
582629 click_mouse ()
583- move_mouse ()
630+ move_mouse_random ()
584631 elif (
585632 rng >= 6
586633 ): # 6-7 25% of the time do realistic human movements for things like https://thehackernews.com/2023/11/lummac2-malware-deploys-new.html
587634 realistic_human_cursor_movement ()
588635 else : # 4-5 25% of the time move the cursor somewhere random and click/move around
589- USER32 . SetCursorPos (
636+ move_mouse_realistically (
590637 int (RESOLUTION_WITHOUT_TASKBAR ["x" ] / random .uniform (1 , 16 )),
591638 int (RESOLUTION_WITHOUT_TASKBAR ["y" ] / random .uniform (1 , 16 )),
592639 )
593640 # Avoid clicking on console windows and suspending execution
594641 if not cursor_over_console_window ():
595642 click_mouse ()
596- move_mouse ()
643+ move_mouse_random ()
597644
598645 if (seconds % (15 + randoff )) == 0 :
599646 # curwind = USER32.GetForegroundWindow()
0 commit comments