iflow-mcp_bethington-cheat-engine-server-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/METADATA +16 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/RECORD +40 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/WHEEL +5 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_bethington_cheat_engine_server_python-0.1.0.dist-info/top_level.txt +1 -0
- server/cheatengine/__init__.py +19 -0
- server/cheatengine/ce_bridge.py +1670 -0
- server/cheatengine/lua_interface.py +460 -0
- server/cheatengine/table_parser.py +1221 -0
- server/config/__init__.py +20 -0
- server/config/settings.py +347 -0
- server/config/whitelist.py +378 -0
- server/gui_automation/__init__.py +43 -0
- server/gui_automation/core/__init__.py +8 -0
- server/gui_automation/core/integration.py +951 -0
- server/gui_automation/demos/__init__.py +8 -0
- server/gui_automation/demos/basic_demo.py +754 -0
- server/gui_automation/demos/notepad_demo.py +460 -0
- server/gui_automation/demos/simple_demo.py +319 -0
- server/gui_automation/tools/__init__.py +8 -0
- server/gui_automation/tools/mcp_tools.py +974 -0
- server/main.py +519 -0
- server/memory/__init__.py +0 -0
- server/memory/analyzer.py +0 -0
- server/memory/reader.py +0 -0
- server/memory/scanner.py +0 -0
- server/memory/symbols.py +0 -0
- server/process/__init__.py +16 -0
- server/process/launcher.py +608 -0
- server/process/manager.py +185 -0
- server/process/monitors.py +202 -0
- server/process/permissions.py +131 -0
- server/process_whitelist.json +119 -0
- server/pyautogui/__init__.py +0 -0
- server/utils/__init__.py +37 -0
- server/utils/data_types.py +368 -0
- server/utils/formatters.py +430 -0
- server/utils/validators.py +340 -0
- server/window_automation/__init__.py +59 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Process Manager Module
|
|
3
|
+
Handles process enumeration, attachment, and management
|
|
4
|
+
Cross-platform version supporting both Windows and Linux
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import psutil
|
|
8
|
+
import logging
|
|
9
|
+
import platform
|
|
10
|
+
from typing import Dict, List, Optional, Any
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
# Detect platform
|
|
16
|
+
IS_WINDOWS = platform.system() == 'Windows'
|
|
17
|
+
|
|
18
|
+
# Windows-specific imports
|
|
19
|
+
if IS_WINDOWS:
|
|
20
|
+
import ctypes
|
|
21
|
+
import ctypes.wintypes
|
|
22
|
+
|
|
23
|
+
# Windows API constants
|
|
24
|
+
PROCESS_QUERY_INFORMATION = 0x0400
|
|
25
|
+
PROCESS_VM_READ = 0x0010
|
|
26
|
+
PROCESS_VM_WRITE = 0x0020
|
|
27
|
+
PROCESS_VM_OPERATION = 0x0008
|
|
28
|
+
PROCESS_ALL_ACCESS = 0x1F0FFF
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ProcessInfo:
|
|
32
|
+
"""Process information structure"""
|
|
33
|
+
pid: int
|
|
34
|
+
name: str
|
|
35
|
+
exe_path: str
|
|
36
|
+
architecture: str
|
|
37
|
+
memory_usage: int
|
|
38
|
+
handle: Optional[int] = None
|
|
39
|
+
access_level: str = "none"
|
|
40
|
+
|
|
41
|
+
class ProcessManager:
|
|
42
|
+
"""Manages process attachment and operations"""
|
|
43
|
+
|
|
44
|
+
def __init__(self):
|
|
45
|
+
self.current_process: Optional[ProcessInfo] = None
|
|
46
|
+
if IS_WINDOWS:
|
|
47
|
+
self.kernel32 = ctypes.windll.kernel32
|
|
48
|
+
else:
|
|
49
|
+
self.kernel32 = None
|
|
50
|
+
|
|
51
|
+
def list_processes(self) -> List[Dict[str, Any]]:
|
|
52
|
+
"""Enumerate all running processes"""
|
|
53
|
+
processes = []
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
for proc in psutil.process_iter(['pid', 'name', 'exe', 'memory_info']):
|
|
57
|
+
try:
|
|
58
|
+
info = proc.info
|
|
59
|
+
if info['name'] and info['pid'] > 0:
|
|
60
|
+
# Get architecture info
|
|
61
|
+
arch = self._get_process_architecture(info['pid'])
|
|
62
|
+
|
|
63
|
+
processes.append({
|
|
64
|
+
'pid': info['pid'],
|
|
65
|
+
'name': info['name'],
|
|
66
|
+
'exe_path': info['exe'] or 'N/A',
|
|
67
|
+
'architecture': arch,
|
|
68
|
+
'memory_usage': info['memory_info'].rss if info['memory_info'] else 0
|
|
69
|
+
})
|
|
70
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Error enumerating processes: {e}")
|
|
75
|
+
|
|
76
|
+
return sorted(processes, key=lambda x: x['name'].lower())
|
|
77
|
+
|
|
78
|
+
def _get_process_architecture(self, pid: int) -> str:
|
|
79
|
+
"""Determine process architecture (32-bit or 64-bit)"""
|
|
80
|
+
try:
|
|
81
|
+
if IS_WINDOWS:
|
|
82
|
+
handle = self.kernel32.OpenProcess(PROCESS_QUERY_INFORMATION, False, pid)
|
|
83
|
+
if not handle:
|
|
84
|
+
return "Unknown"
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Check if process is WOW64 (32-bit on 64-bit Windows)
|
|
88
|
+
is_wow64 = ctypes.wintypes.BOOL()
|
|
89
|
+
if self.kernel32.IsWow64Process(handle, ctypes.byref(is_wow64)):
|
|
90
|
+
if is_wow64.value:
|
|
91
|
+
return "x86"
|
|
92
|
+
else:
|
|
93
|
+
# Check system architecture
|
|
94
|
+
if platform.machine().endswith('64'):
|
|
95
|
+
return "x64"
|
|
96
|
+
else:
|
|
97
|
+
return "x86"
|
|
98
|
+
return "Unknown"
|
|
99
|
+
finally:
|
|
100
|
+
self.kernel32.CloseHandle(handle)
|
|
101
|
+
else:
|
|
102
|
+
# Linux: Use /proc filesystem
|
|
103
|
+
try:
|
|
104
|
+
with open(f'/proc/{pid}/exe', 'rb') as f:
|
|
105
|
+
exe_path = f.read()
|
|
106
|
+
# Check if executable path contains 32-bit indicators
|
|
107
|
+
if b'32' in exe_path or b'x86' in exe_path:
|
|
108
|
+
return "x86"
|
|
109
|
+
# Check system architecture
|
|
110
|
+
if platform.machine().endswith('64'):
|
|
111
|
+
return "x64"
|
|
112
|
+
return "x86"
|
|
113
|
+
except (FileNotFoundError, PermissionError):
|
|
114
|
+
return "Unknown"
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Error getting process architecture: {e}")
|
|
117
|
+
return "Unknown"
|
|
118
|
+
|
|
119
|
+
def attach_process(self, pid: int) -> ProcessInfo:
|
|
120
|
+
"""Attach to a process"""
|
|
121
|
+
try:
|
|
122
|
+
proc = psutil.Process(pid)
|
|
123
|
+
|
|
124
|
+
process_info = ProcessInfo(
|
|
125
|
+
pid=pid,
|
|
126
|
+
name=proc.name(),
|
|
127
|
+
exe_path=proc.exe() or 'N/A',
|
|
128
|
+
architecture=self._get_process_architecture(pid),
|
|
129
|
+
memory_usage=proc.memory_info().rss,
|
|
130
|
+
handle=pid if not IS_WINDOWS else None,
|
|
131
|
+
access_level="full" if IS_WINDOWS else "limited"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
self.current_process = process_info
|
|
135
|
+
logger.info(f"Attached to process {pid} ({process_info.name})")
|
|
136
|
+
return process_info
|
|
137
|
+
|
|
138
|
+
except psutil.NoSuchProcess:
|
|
139
|
+
raise Exception(f"Process {pid} not found")
|
|
140
|
+
except psutil.AccessDenied:
|
|
141
|
+
raise Exception(f"Access denied to process {pid}")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
raise Exception(f"Failed to attach to process {pid}: {e}")
|
|
144
|
+
|
|
145
|
+
def detach_process(self) -> bool:
|
|
146
|
+
"""Detach from current process"""
|
|
147
|
+
try:
|
|
148
|
+
if self.current_process:
|
|
149
|
+
if IS_WINDOWS and self.current_process.handle:
|
|
150
|
+
self.kernel32.CloseHandle(self.current_process.handle)
|
|
151
|
+
self.current_process = None
|
|
152
|
+
logger.info("Detached from process")
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"Error detaching from process: {e}")
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
def get_process_info(self, pid: int) -> Dict[str, Any]:
|
|
160
|
+
"""Get detailed information about a process"""
|
|
161
|
+
try:
|
|
162
|
+
proc = psutil.Process(pid)
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
'pid': pid,
|
|
166
|
+
'name': proc.name(),
|
|
167
|
+
'exe': proc.exe() or 'N/A',
|
|
168
|
+
'cwd': proc.cwd() or 'N/A',
|
|
169
|
+
'cmdline': proc.cmdline(),
|
|
170
|
+
'create_time': proc.create_time(),
|
|
171
|
+
'status': proc.status(),
|
|
172
|
+
'username': proc.username(),
|
|
173
|
+
'memory_info': {
|
|
174
|
+
'rss': proc.memory_info().rss,
|
|
175
|
+
'vms': proc.memory_info().vms
|
|
176
|
+
},
|
|
177
|
+
'cpu_percent': proc.cpu_percent(),
|
|
178
|
+
'num_threads': proc.num_threads(),
|
|
179
|
+
'architecture': self._get_process_architecture(pid),
|
|
180
|
+
'platform': platform.system()
|
|
181
|
+
}
|
|
182
|
+
except psutil.NoSuchProcess:
|
|
183
|
+
raise Exception(f"Process {pid} not found")
|
|
184
|
+
except Exception as e:
|
|
185
|
+
raise Exception(f"Failed to get process info: {e}")
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Process Monitors Module
|
|
3
|
+
Handles process state monitoring and change detection
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
import psutil
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Dict, List, Callable, Optional
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ProcessState:
|
|
18
|
+
"""Process state snapshot"""
|
|
19
|
+
pid: int
|
|
20
|
+
name: str
|
|
21
|
+
status: str
|
|
22
|
+
memory_usage: int
|
|
23
|
+
cpu_percent: float
|
|
24
|
+
num_threads: int
|
|
25
|
+
timestamp: datetime
|
|
26
|
+
|
|
27
|
+
class ProcessMonitor:
|
|
28
|
+
"""Monitors process state changes"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.monitored_processes: Dict[int, ProcessState] = {}
|
|
32
|
+
self.callbacks: List[Callable] = []
|
|
33
|
+
self.monitoring = False
|
|
34
|
+
self.monitor_thread: Optional[threading.Thread] = None
|
|
35
|
+
self.update_interval = 5.0 # seconds
|
|
36
|
+
|
|
37
|
+
def add_process(self, pid: int):
|
|
38
|
+
"""Add a process to monitoring"""
|
|
39
|
+
try:
|
|
40
|
+
proc = psutil.Process(pid)
|
|
41
|
+
state = ProcessState(
|
|
42
|
+
pid=pid,
|
|
43
|
+
name=proc.name(),
|
|
44
|
+
status=proc.status(),
|
|
45
|
+
memory_usage=proc.memory_info().rss,
|
|
46
|
+
cpu_percent=proc.cpu_percent(),
|
|
47
|
+
num_threads=proc.num_threads(),
|
|
48
|
+
timestamp=datetime.now()
|
|
49
|
+
)
|
|
50
|
+
self.monitored_processes[pid] = state
|
|
51
|
+
logger.info(f"Added process {pid} to monitoring")
|
|
52
|
+
|
|
53
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
|
|
54
|
+
logger.error(f"Cannot monitor process {pid}: {e}")
|
|
55
|
+
raise
|
|
56
|
+
|
|
57
|
+
def remove_process(self, pid: int):
|
|
58
|
+
"""Remove a process from monitoring"""
|
|
59
|
+
if pid in self.monitored_processes:
|
|
60
|
+
del self.monitored_processes[pid]
|
|
61
|
+
logger.info(f"Removed process {pid} from monitoring")
|
|
62
|
+
|
|
63
|
+
def add_callback(self, callback: Callable[[int, str, Dict], None]):
|
|
64
|
+
"""Add a callback for process state changes
|
|
65
|
+
|
|
66
|
+
Callback signature: callback(pid, event_type, data)
|
|
67
|
+
Event types: 'terminated', 'memory_change', 'status_change'
|
|
68
|
+
"""
|
|
69
|
+
self.callbacks.append(callback)
|
|
70
|
+
|
|
71
|
+
def start_monitoring(self):
|
|
72
|
+
"""Start the monitoring thread"""
|
|
73
|
+
if self.monitoring:
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
self.monitoring = True
|
|
77
|
+
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
|
|
78
|
+
self.monitor_thread.start()
|
|
79
|
+
logger.info("Process monitoring started")
|
|
80
|
+
|
|
81
|
+
def stop_monitoring(self):
|
|
82
|
+
"""Stop the monitoring thread"""
|
|
83
|
+
self.monitoring = False
|
|
84
|
+
if self.monitor_thread:
|
|
85
|
+
self.monitor_thread.join(timeout=2.0)
|
|
86
|
+
logger.info("Process monitoring stopped")
|
|
87
|
+
|
|
88
|
+
def _monitor_loop(self):
|
|
89
|
+
"""Main monitoring loop"""
|
|
90
|
+
while self.monitoring:
|
|
91
|
+
try:
|
|
92
|
+
self._check_processes()
|
|
93
|
+
time.sleep(self.update_interval)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"Error in monitoring loop: {e}")
|
|
96
|
+
time.sleep(1.0)
|
|
97
|
+
|
|
98
|
+
def _check_processes(self):
|
|
99
|
+
"""Check all monitored processes for changes"""
|
|
100
|
+
for pid in list(self.monitored_processes.keys()):
|
|
101
|
+
try:
|
|
102
|
+
self._check_single_process(pid)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"Error checking process {pid}: {e}")
|
|
105
|
+
|
|
106
|
+
def _check_single_process(self, pid: int):
|
|
107
|
+
"""Check a single process for changes"""
|
|
108
|
+
old_state = self.monitored_processes[pid]
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
proc = psutil.Process(pid)
|
|
112
|
+
|
|
113
|
+
# Get current state
|
|
114
|
+
current_state = ProcessState(
|
|
115
|
+
pid=pid,
|
|
116
|
+
name=proc.name(),
|
|
117
|
+
status=proc.status(),
|
|
118
|
+
memory_usage=proc.memory_info().rss,
|
|
119
|
+
cpu_percent=proc.cpu_percent(),
|
|
120
|
+
num_threads=proc.num_threads(),
|
|
121
|
+
timestamp=datetime.now()
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Check for changes
|
|
125
|
+
self._detect_changes(old_state, current_state)
|
|
126
|
+
|
|
127
|
+
# Update stored state
|
|
128
|
+
self.monitored_processes[pid] = current_state
|
|
129
|
+
|
|
130
|
+
except psutil.NoSuchProcess:
|
|
131
|
+
# Process terminated
|
|
132
|
+
self._notify_callbacks(pid, 'terminated', {'last_state': old_state})
|
|
133
|
+
self.remove_process(pid)
|
|
134
|
+
|
|
135
|
+
except (psutil.AccessDenied, psutil.ZombieProcess) as e:
|
|
136
|
+
logger.warning(f"Cannot access process {pid}: {e}")
|
|
137
|
+
|
|
138
|
+
def _detect_changes(self, old_state: ProcessState, new_state: ProcessState):
|
|
139
|
+
"""Detect and report changes between states"""
|
|
140
|
+
|
|
141
|
+
# Status change
|
|
142
|
+
if old_state.status != new_state.status:
|
|
143
|
+
self._notify_callbacks(
|
|
144
|
+
new_state.pid,
|
|
145
|
+
'status_change',
|
|
146
|
+
{
|
|
147
|
+
'old_status': old_state.status,
|
|
148
|
+
'new_status': new_state.status
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Significant memory change (>10MB or >10%)
|
|
153
|
+
memory_diff = abs(new_state.memory_usage - old_state.memory_usage)
|
|
154
|
+
memory_percent_change = memory_diff / old_state.memory_usage if old_state.memory_usage > 0 else 0
|
|
155
|
+
|
|
156
|
+
if memory_diff > 10 * 1024 * 1024 or memory_percent_change > 0.1:
|
|
157
|
+
self._notify_callbacks(
|
|
158
|
+
new_state.pid,
|
|
159
|
+
'memory_change',
|
|
160
|
+
{
|
|
161
|
+
'old_memory': old_state.memory_usage,
|
|
162
|
+
'new_memory': new_state.memory_usage,
|
|
163
|
+
'change': memory_diff,
|
|
164
|
+
'percent_change': memory_percent_change
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Thread count change
|
|
169
|
+
if old_state.num_threads != new_state.num_threads:
|
|
170
|
+
self._notify_callbacks(
|
|
171
|
+
new_state.pid,
|
|
172
|
+
'thread_change',
|
|
173
|
+
{
|
|
174
|
+
'old_threads': old_state.num_threads,
|
|
175
|
+
'new_threads': new_state.num_threads
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def _notify_callbacks(self, pid: int, event_type: str, data: Dict):
|
|
180
|
+
"""Notify all callbacks of an event"""
|
|
181
|
+
for callback in self.callbacks:
|
|
182
|
+
try:
|
|
183
|
+
callback(pid, event_type, data)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
logger.error(f"Error in process monitor callback: {e}")
|
|
186
|
+
|
|
187
|
+
def get_process_state(self, pid: int) -> Optional[ProcessState]:
|
|
188
|
+
"""Get current state of a monitored process"""
|
|
189
|
+
return self.monitored_processes.get(pid)
|
|
190
|
+
|
|
191
|
+
def get_all_states(self) -> Dict[int, ProcessState]:
|
|
192
|
+
"""Get all current process states"""
|
|
193
|
+
return self.monitored_processes.copy()
|
|
194
|
+
|
|
195
|
+
def set_update_interval(self, seconds: float):
|
|
196
|
+
"""Set the monitoring update interval"""
|
|
197
|
+
if seconds > 0:
|
|
198
|
+
self.update_interval = seconds
|
|
199
|
+
|
|
200
|
+
def __del__(self):
|
|
201
|
+
"""Cleanup on destruction"""
|
|
202
|
+
self.stop_monitoring()
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Process Permissions Module
|
|
3
|
+
Handles security and permission validation
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import ctypes
|
|
7
|
+
import ctypes.wintypes
|
|
8
|
+
import os
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Set, List
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class PermissionChecker:
|
|
15
|
+
"""Handles permission and security checks"""
|
|
16
|
+
|
|
17
|
+
def __init__(self):
|
|
18
|
+
self.kernel32 = ctypes.windll.kernel32
|
|
19
|
+
self.advapi32 = ctypes.windll.advapi32
|
|
20
|
+
|
|
21
|
+
def has_debug_privileges(self) -> bool:
|
|
22
|
+
"""Check if current process has debug privileges"""
|
|
23
|
+
try:
|
|
24
|
+
# Get current process token
|
|
25
|
+
token = ctypes.wintypes.HANDLE()
|
|
26
|
+
if not self.advapi32.OpenProcessToken(
|
|
27
|
+
self.kernel32.GetCurrentProcess(),
|
|
28
|
+
0x0020, # TOKEN_ADJUST_PRIVILEGES
|
|
29
|
+
ctypes.byref(token)
|
|
30
|
+
):
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# Check for SeDebugPrivilege
|
|
35
|
+
privilege_name = "SeDebugPrivilege"
|
|
36
|
+
luid = ctypes.wintypes.LUID()
|
|
37
|
+
|
|
38
|
+
if not self.advapi32.LookupPrivilegeValueW(
|
|
39
|
+
None,
|
|
40
|
+
privilege_name,
|
|
41
|
+
ctypes.byref(luid)
|
|
42
|
+
):
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
# Check if privilege is enabled
|
|
46
|
+
privilege_set = ctypes.create_string_buffer(16) # PRIVILEGE_SET structure
|
|
47
|
+
privilege_length = ctypes.wintypes.DWORD(16)
|
|
48
|
+
result = ctypes.wintypes.BOOL()
|
|
49
|
+
|
|
50
|
+
if self.advapi32.PrivilegeCheck(
|
|
51
|
+
token,
|
|
52
|
+
privilege_set,
|
|
53
|
+
ctypes.byref(result)
|
|
54
|
+
):
|
|
55
|
+
return bool(result.value)
|
|
56
|
+
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
finally:
|
|
60
|
+
self.kernel32.CloseHandle(token)
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.warning(f"Failed to check debug privileges: {e}")
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
def is_elevated(self) -> bool:
|
|
67
|
+
"""Check if current process is running with elevated privileges"""
|
|
68
|
+
try:
|
|
69
|
+
return ctypes.windll.shell32.IsUserAnAdmin()
|
|
70
|
+
except Exception:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def can_access_process(self, pid: int, access_level: str = "read") -> tuple[bool, str]:
|
|
74
|
+
"""Check if we can access a specific process with given access level"""
|
|
75
|
+
try:
|
|
76
|
+
if access_level == "read":
|
|
77
|
+
access_rights = 0x0400 | 0x0010 # PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
|
|
78
|
+
elif access_level == "debug":
|
|
79
|
+
access_rights = 0x1F0FFF # PROCESS_ALL_ACCESS
|
|
80
|
+
else:
|
|
81
|
+
return False, f"Invalid access level: {access_level}"
|
|
82
|
+
|
|
83
|
+
handle = self.kernel32.OpenProcess(access_rights, False, pid)
|
|
84
|
+
if handle:
|
|
85
|
+
self.kernel32.CloseHandle(handle)
|
|
86
|
+
return True, "Access granted"
|
|
87
|
+
else:
|
|
88
|
+
error_code = ctypes.get_last_error()
|
|
89
|
+
return False, f"Access denied (Error {error_code})"
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return False, f"Exception checking access: {e}"
|
|
93
|
+
|
|
94
|
+
def get_security_recommendations(self) -> List[str]:
|
|
95
|
+
"""Get security recommendations for current environment"""
|
|
96
|
+
recommendations = []
|
|
97
|
+
|
|
98
|
+
if not self.is_elevated():
|
|
99
|
+
recommendations.append("Consider running as administrator for full functionality")
|
|
100
|
+
|
|
101
|
+
if not self.has_debug_privileges():
|
|
102
|
+
recommendations.append("Debug privileges not available - some features may be limited")
|
|
103
|
+
|
|
104
|
+
return recommendations
|
|
105
|
+
|
|
106
|
+
def validate_address(self, address: int) -> bool:
|
|
107
|
+
"""Validate that an address is within reasonable bounds"""
|
|
108
|
+
# Basic sanity checks for memory addresses
|
|
109
|
+
if address < 0:
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
# Check for obviously invalid addresses
|
|
113
|
+
if address < 0x1000: # Null page
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
# Check for addresses that are too high (basic check)
|
|
117
|
+
if address > 0x7FFFFFFFFFFF: # Max user-mode address on x64
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
def validate_size(self, size: int) -> bool:
|
|
123
|
+
"""Validate that a size parameter is reasonable"""
|
|
124
|
+
if size <= 0:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
# Limit maximum read size to prevent abuse
|
|
128
|
+
if size > 1024 * 1024 * 100: # 100MB max
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
return True
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabled": true,
|
|
3
|
+
"description": "Process whitelist for MCP Cheat Engine Server",
|
|
4
|
+
"last_updated": "2025-07-31T01:58:10.838750",
|
|
5
|
+
"entries": [
|
|
6
|
+
{
|
|
7
|
+
"process_name": "notepad.exe",
|
|
8
|
+
"description": "Windows Notepad",
|
|
9
|
+
"category": "system",
|
|
10
|
+
"added_date": "2025-07-31T01:58:10.497479",
|
|
11
|
+
"enabled": true,
|
|
12
|
+
"exact_match": true
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"process_name": "code.exe",
|
|
16
|
+
"description": "Visual Studio Code",
|
|
17
|
+
"category": "development",
|
|
18
|
+
"added_date": "2025-07-31T01:58:10.497487",
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"exact_match": true
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"process_name": "devenv.exe",
|
|
24
|
+
"description": "Visual Studio",
|
|
25
|
+
"category": "development",
|
|
26
|
+
"added_date": "2025-07-31T01:58:10.497490",
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"exact_match": true
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"process_name": "windbg.exe",
|
|
32
|
+
"description": "Windows Debugger",
|
|
33
|
+
"category": "development",
|
|
34
|
+
"added_date": "2025-07-31T01:58:10.497495",
|
|
35
|
+
"enabled": true,
|
|
36
|
+
"exact_match": true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"process_name": "x64dbg.exe",
|
|
40
|
+
"description": "x64dbg Debugger",
|
|
41
|
+
"category": "development",
|
|
42
|
+
"added_date": "2025-07-31T01:58:10.497497",
|
|
43
|
+
"enabled": true,
|
|
44
|
+
"exact_match": true
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"process_name": "ollydbg.exe",
|
|
48
|
+
"description": "OllyDbg Debugger",
|
|
49
|
+
"category": "development",
|
|
50
|
+
"added_date": "2025-07-31T01:58:10.497498",
|
|
51
|
+
"enabled": true,
|
|
52
|
+
"exact_match": true
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"process_name": "calc.exe",
|
|
56
|
+
"description": "Windows Calculator",
|
|
57
|
+
"category": "system",
|
|
58
|
+
"added_date": "2025-07-31T01:58:10.497500",
|
|
59
|
+
"enabled": true,
|
|
60
|
+
"exact_match": true
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"process_name": "mspaint.exe",
|
|
64
|
+
"description": "Microsoft Paint",
|
|
65
|
+
"category": "system",
|
|
66
|
+
"added_date": "2025-07-31T01:58:10.497501",
|
|
67
|
+
"enabled": true,
|
|
68
|
+
"exact_match": true
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"process_name": "wordpad.exe",
|
|
72
|
+
"description": "Windows WordPad",
|
|
73
|
+
"category": "system",
|
|
74
|
+
"added_date": "2025-07-31T01:58:10.497503",
|
|
75
|
+
"enabled": true,
|
|
76
|
+
"exact_match": true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"process_name": "minesweeper.exe",
|
|
80
|
+
"description": "Minesweeper",
|
|
81
|
+
"category": "game",
|
|
82
|
+
"added_date": "2025-07-31T01:58:10.497504",
|
|
83
|
+
"enabled": true,
|
|
84
|
+
"exact_match": true
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"process_name": "solitaire.exe",
|
|
88
|
+
"description": "Solitaire",
|
|
89
|
+
"category": "game",
|
|
90
|
+
"added_date": "2025-07-31T01:58:10.497505",
|
|
91
|
+
"enabled": true,
|
|
92
|
+
"exact_match": true
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"process_name": "test*.exe",
|
|
96
|
+
"description": "Test Applications",
|
|
97
|
+
"category": "test",
|
|
98
|
+
"added_date": "2025-07-31T01:58:10.497507",
|
|
99
|
+
"enabled": true,
|
|
100
|
+
"exact_match": false
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"process_name": "demo*.exe",
|
|
104
|
+
"description": "Demo Applications",
|
|
105
|
+
"category": "test",
|
|
106
|
+
"added_date": "2025-07-31T01:58:10.497509",
|
|
107
|
+
"enabled": true,
|
|
108
|
+
"exact_match": false
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"process_name": "dbengine-x86_64.exe",
|
|
112
|
+
"description": "Database Engine x64",
|
|
113
|
+
"category": "development",
|
|
114
|
+
"added_date": "2025-07-31T05:02:00.000000",
|
|
115
|
+
"enabled": true,
|
|
116
|
+
"exact_match": true
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
File without changes
|
server/utils/__init__.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility Functions Module
|
|
3
|
+
Provides validation, formatting, and data type utilities
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .validators import (
|
|
7
|
+
validate_address, validate_size, validate_pattern,
|
|
8
|
+
validate_process_identifier, validate_data_type,
|
|
9
|
+
sanitize_filename, validate_region_list
|
|
10
|
+
)
|
|
11
|
+
from .formatters import (
|
|
12
|
+
format_memory_data, format_raw_bytes, format_process_info,
|
|
13
|
+
format_size, format_timestamp, format_hex_dump, format_scan_results
|
|
14
|
+
)
|
|
15
|
+
from .data_types import (
|
|
16
|
+
DataType, Architecture, MemoryProtection,
|
|
17
|
+
ProcessSnapshot, MemoryBlock, ScanResult, PointerChain,
|
|
18
|
+
DataTypeConverter, PatternMatcher, AddressCalculator,
|
|
19
|
+
AnalysisContext, SimpleCache
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Validators
|
|
24
|
+
'validate_address', 'validate_size', 'validate_pattern',
|
|
25
|
+
'validate_process_identifier', 'validate_data_type',
|
|
26
|
+
'sanitize_filename', 'validate_region_list',
|
|
27
|
+
|
|
28
|
+
# Formatters
|
|
29
|
+
'format_memory_data', 'format_raw_bytes', 'format_process_info',
|
|
30
|
+
'format_size', 'format_timestamp', 'format_hex_dump', 'format_scan_results',
|
|
31
|
+
|
|
32
|
+
# Data Types
|
|
33
|
+
'DataType', 'Architecture', 'MemoryProtection',
|
|
34
|
+
'ProcessSnapshot', 'MemoryBlock', 'ScanResult', 'PointerChain',
|
|
35
|
+
'DataTypeConverter', 'PatternMatcher', 'AddressCalculator',
|
|
36
|
+
'AnalysisContext', 'SimpleCache'
|
|
37
|
+
]
|