h4-debug 0.2.0__tar.gz → 0.2.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: h4-debug
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Advanced application proxy debugger and F12-style web console
5
5
  Author-email: h4 <h4@example.com>
6
6
  Requires-Python: >=3.8
@@ -9,6 +9,7 @@ Requires-Dist: fastapi>=0.100
9
9
  Requires-Dist: uvicorn[standard]>=0.20
10
10
  Requires-Dist: websockets>=11.0
11
11
  Requires-Dist: Pillow>=10.0.0
12
+ Requires-Dist: psutil>=5.9.0
12
13
 
13
14
  # h4-debug
14
15
 
@@ -157,24 +157,117 @@ def read_process_memory(hProcess, address, size):
157
157
  return buffer.raw[:bytesRead.value]
158
158
  return b""
159
159
 
160
+ def psutil_daemon(pid, client, stop_event):
161
+ import psutil
162
+ import threading
163
+ try:
164
+ proc = psutil.Process(pid)
165
+ except psutil.NoSuchProcess:
166
+ return
167
+
168
+ seen_files = set()
169
+ seen_conns = set()
170
+
171
+ while not stop_event.is_set():
172
+ try:
173
+ # Poll network
174
+ conns = proc.connections(kind='all')
175
+ for c in conns:
176
+ conn_id = f"{c.laddr}-{c.raddr}-{c.status}"
177
+ if conn_id not in seen_conns:
178
+ seen_conns.add(conn_id)
179
+ addr = str(c.raddr) if c.raddr else str(c.laddr)
180
+ client.send("Network", "connect", {"address": f"{c.status} {addr}"})
181
+
182
+ # Poll files
183
+ files = proc.open_files()
184
+ for f in files:
185
+ if f.path not in seen_files:
186
+ seen_files.add(f.path)
187
+ client.send("Disk", "open", {"file": f.path, "mode": f.mode if hasattr(f, 'mode') else 'r'})
188
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
189
+ break
190
+ except Exception:
191
+ pass
192
+
193
+ time.sleep(0.1)
194
+
195
+ class SECURITY_ATTRIBUTES(ctypes.Structure):
196
+ _fields_ = [
197
+ ("nLength", wintypes.DWORD),
198
+ ("lpSecurityDescriptor", ctypes.c_void_p),
199
+ ("bInheritHandle", wintypes.BOOL),
200
+ ]
201
+
160
202
  def debug_process(command, client):
161
203
  si = STARTUPINFO()
162
204
  si.cb = ctypes.sizeof(si)
163
205
  pi = PROCESS_INFORMATION()
164
206
 
207
+ # Create Pipe for stdout/stderr
208
+ hReadPipe = wintypes.HANDLE()
209
+ hWritePipe = wintypes.HANDLE()
210
+ sa = SECURITY_ATTRIBUTES()
211
+ sa.nLength = ctypes.sizeof(SECURITY_ATTRIBUTES)
212
+ sa.bInheritHandle = True
213
+ sa.lpSecurityDescriptor = None
214
+
215
+ if kernel32.CreatePipe(ctypes.byref(hReadPipe), ctypes.byref(hWritePipe), ctypes.byref(sa), 0):
216
+ # Ensure the read handle to the pipe is not inherited
217
+ kernel32.SetHandleInformation(hReadPipe, 1, 0) # HANDLE_FLAG_INHERIT = 1
218
+
219
+ si.hStdError = hWritePipe
220
+ si.hStdOutput = hWritePipe
221
+ si.dwFlags |= 0x00000100 # STARTF_USESTDHANDLES
222
+
165
223
  creation_flags = DEBUG_PROCESS
166
224
 
167
225
  # Convert command list to string for Windows
168
226
  cmd_str = " ".join(f'"{c}"' if " " in c else c for c in command)
169
227
 
170
228
  if not kernel32.CreateProcessW(
171
- None, ctypes.c_wchar_p(cmd_str), None, None, False,
229
+ None, ctypes.c_wchar_p(cmd_str), None, None, True, # bInheritHandles=True
172
230
  creation_flags, None, None, ctypes.byref(si), ctypes.byref(pi)):
173
- client.send("System", "error", {"text": f"Failed to CreateProcess: {ctypes.GetLastError()}"})
231
+ err = ctypes.GetLastError()
232
+ err_msg = f"Failed to CreateProcess: {err}"
233
+ if err == 5:
234
+ err_msg += " (Access Denied). The executable may require Administrator privileges, or it may be blocked by Windows Defender/DRM. Try running h4-debug from an Administrator terminal."
235
+ elif err == 740:
236
+ err_msg += " (Elevation Required). The executable requires Administrator privileges. Please run h4-debug from an Administrator terminal."
237
+ client.send("System", "error", {"text": err_msg})
238
+ print(f"[h4-debug] {err_msg}")
239
+ if hWritePipe:
240
+ kernel32.CloseHandle(hReadPipe)
241
+ kernel32.CloseHandle(hWritePipe)
174
242
  return
175
243
 
244
+ # Close our write end of the pipe so the read thread unblocks when the process exits
245
+ if hWritePipe:
246
+ kernel32.CloseHandle(hWritePipe)
247
+
248
+ def stream_pipe(handle, client):
249
+ buffer = ctypes.create_string_buffer(4096)
250
+ bytes_read = wintypes.DWORD()
251
+ while True:
252
+ if not kernel32.ReadFile(handle, buffer, 4096, ctypes.byref(bytes_read), None) or bytes_read.value == 0:
253
+ break
254
+ text = buffer.raw[:bytes_read.value].decode('utf-8', 'replace').strip('\r\n\x00')
255
+ if text:
256
+ for line in text.splitlines():
257
+ client.send("Console", "stdout", {"text": line})
258
+ kernel32.CloseHandle(handle)
259
+
260
+ pipe_thread = None
261
+ if hReadPipe:
262
+ pipe_thread = threading.Thread(target=stream_pipe, args=(hReadPipe, client), daemon=True)
263
+ pipe_thread.start()
264
+
176
265
  process_handle = None
177
266
  debug_event = DEBUG_EVENT()
267
+
268
+ import threading
269
+ daemon_stop_event = threading.Event()
270
+ daemon_thread = None
178
271
 
179
272
  while True:
180
273
  if not kernel32.WaitForDebugEvent(ctypes.byref(debug_event), 1000): # 1 sec timeout to yield
@@ -189,6 +282,11 @@ def debug_process(command, client):
189
282
  if debug_event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT:
190
283
  process_handle = debug_event.u.CreateProcessInfo.hProcess
191
284
  client.send("System", "info", {"text": f"Process Created: PID {debug_event.dwProcessId}"})
285
+
286
+ # Start psutil daemon for disk and network tracking
287
+ daemon_thread = threading.Thread(target=psutil_daemon, args=(debug_event.dwProcessId, client, daemon_stop_event), daemon=True)
288
+ daemon_thread.start()
289
+
192
290
  if debug_event.u.CreateProcessInfo.hFile:
193
291
  kernel32.CloseHandle(debug_event.u.CreateProcessInfo.hFile)
194
292
 
@@ -217,11 +315,16 @@ def debug_process(command, client):
217
315
 
218
316
  elif debug_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT:
219
317
  client.send("System", "info", {"text": f"Process Exited with code {debug_event.u.ExitProcess.dwExitCode}"})
318
+ daemon_stop_event.set()
220
319
  kernel32.ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, continue_status)
221
320
  break
222
321
 
223
322
  kernel32.ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, continue_status)
224
323
 
324
+ daemon_stop_event.set()
325
+ if daemon_thread:
326
+ daemon_thread.join(timeout=1.0)
327
+
225
328
  if process_handle:
226
329
  kernel32.CloseHandle(pi.hThread)
227
330
  kernel32.CloseHandle(pi.hProcess)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: h4-debug
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Advanced application proxy debugger and F12-style web console
5
5
  Author-email: h4 <h4@example.com>
6
6
  Requires-Python: >=3.8
@@ -9,6 +9,7 @@ Requires-Dist: fastapi>=0.100
9
9
  Requires-Dist: uvicorn[standard]>=0.20
10
10
  Requires-Dist: websockets>=11.0
11
11
  Requires-Dist: Pillow>=10.0.0
12
+ Requires-Dist: psutil>=5.9.0
12
13
 
13
14
  # h4-debug
14
15
 
@@ -2,3 +2,4 @@ fastapi>=0.100
2
2
  uvicorn[standard]>=0.20
3
3
  websockets>=11.0
4
4
  Pillow>=10.0.0
5
+ psutil>=5.9.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "h4-debug"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Advanced application proxy debugger and F12-style web console"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -16,6 +16,7 @@ dependencies = [
16
16
  "uvicorn[standard]>=0.20",
17
17
  "websockets>=11.0",
18
18
  "Pillow>=10.0.0",
19
+ "psutil>=5.9.0",
19
20
  ]
20
21
 
21
22
  [project.scripts]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes