h4-debug 0.2.0__tar.gz → 0.2.1__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.1
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,110 @@ 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
231
  client.send("System", "error", {"text": f"Failed to CreateProcess: {ctypes.GetLastError()}"})
232
+ if hWritePipe:
233
+ kernel32.CloseHandle(hReadPipe)
234
+ kernel32.CloseHandle(hWritePipe)
174
235
  return
175
236
 
237
+ # Close our write end of the pipe so the read thread unblocks when the process exits
238
+ if hWritePipe:
239
+ kernel32.CloseHandle(hWritePipe)
240
+
241
+ def stream_pipe(handle, client):
242
+ buffer = ctypes.create_string_buffer(4096)
243
+ bytes_read = wintypes.DWORD()
244
+ while True:
245
+ if not kernel32.ReadFile(handle, buffer, 4096, ctypes.byref(bytes_read), None) or bytes_read.value == 0:
246
+ break
247
+ text = buffer.raw[:bytes_read.value].decode('utf-8', 'replace').strip('\r\n\x00')
248
+ if text:
249
+ for line in text.splitlines():
250
+ client.send("Console", "stdout", {"text": line})
251
+ kernel32.CloseHandle(handle)
252
+
253
+ pipe_thread = None
254
+ if hReadPipe:
255
+ pipe_thread = threading.Thread(target=stream_pipe, args=(hReadPipe, client), daemon=True)
256
+ pipe_thread.start()
257
+
176
258
  process_handle = None
177
259
  debug_event = DEBUG_EVENT()
260
+
261
+ import threading
262
+ daemon_stop_event = threading.Event()
263
+ daemon_thread = None
178
264
 
179
265
  while True:
180
266
  if not kernel32.WaitForDebugEvent(ctypes.byref(debug_event), 1000): # 1 sec timeout to yield
@@ -189,6 +275,11 @@ def debug_process(command, client):
189
275
  if debug_event.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT:
190
276
  process_handle = debug_event.u.CreateProcessInfo.hProcess
191
277
  client.send("System", "info", {"text": f"Process Created: PID {debug_event.dwProcessId}"})
278
+
279
+ # Start psutil daemon for disk and network tracking
280
+ daemon_thread = threading.Thread(target=psutil_daemon, args=(debug_event.dwProcessId, client, daemon_stop_event), daemon=True)
281
+ daemon_thread.start()
282
+
192
283
  if debug_event.u.CreateProcessInfo.hFile:
193
284
  kernel32.CloseHandle(debug_event.u.CreateProcessInfo.hFile)
194
285
 
@@ -217,11 +308,16 @@ def debug_process(command, client):
217
308
 
218
309
  elif debug_event.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT:
219
310
  client.send("System", "info", {"text": f"Process Exited with code {debug_event.u.ExitProcess.dwExitCode}"})
311
+ daemon_stop_event.set()
220
312
  kernel32.ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, continue_status)
221
313
  break
222
314
 
223
315
  kernel32.ContinueDebugEvent(debug_event.dwProcessId, debug_event.dwThreadId, continue_status)
224
316
 
317
+ daemon_stop_event.set()
318
+ if daemon_thread:
319
+ daemon_thread.join(timeout=1.0)
320
+
225
321
  if process_handle:
226
322
  kernel32.CloseHandle(pi.hThread)
227
323
  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.1
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.1"
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