Skip to content

Commit 2d58bba

Browse files
committed
make human.py mouse movement more realistic
1 parent f310633 commit 2d58bba

1 file changed

Lines changed: 60 additions & 13 deletions

File tree

  • analyzer/windows/modules/auxiliary

analyzer/windows/modules/auxiliary/human.py

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import contextlib
88
import logging
99
import random
10+
import time
11+
import math
1012
import re
1113
import traceback
1214
from 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.
284331
def 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

312359
def click_mouse():
@@ -320,7 +367,7 @@ def click_mouse():
320367
def 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

Comments
 (0)