├── .gitignore ├── LICENSE ├── README.md ├── botnet ├── __init__.py ├── fabfile.py └── utilities.py ├── hosts.txt.sample ├── requirements.txt └── start.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Hosts file 2 | hosts.txt 3 | 4 | # Paramiko logs 5 | paramiko.log 6 | 7 | # .pyc files 8 | *.pyc 9 | 10 | # Python virtualenv 11 | venv 12 | venv3 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Marco Rosa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CnC-Botnet-in-Python 2 | C&C Botnet written in Python 3 | 4 | ## Description 5 | Simple botnet written in Python using fabric. 6 |

7 | The author is not responsible for the use of this code. 8 | 9 | ## Hosts 10 | It is possible to load hosts from a file _hosts.txt_ included in the main directory of the project. 11 | The default format for this file is: 12 | ```bash 13 | username@host:port password 14 | ``` 15 | 16 | If the port number is not declared, port 22 is used: 17 | ```bash 18 | username@host password 19 | ``` 20 | SSH connection is the default authentication way, so if the host knows the public key of the user, it is not necessary to indicate the password: 21 | ```bash 22 | username@host 23 | ``` 24 | 25 | ## Usage 26 | To start the application, simply download the repository 27 | ```bash 28 | git clone https://github.com/marcorosa/CnC-Botnet-in-Python 29 | cd CnC-Botnet-in-Python 30 | ``` 31 | 32 | Install the dependencies 33 | ```bash 34 | pip install -r requirements.txt 35 | ``` 36 | 37 | Create the _hosts.txt_ file (optional, see above), and run the start script 38 | ```bash 39 | python start.py 40 | ``` 41 | 42 | ## Example 43 | ``` 44 | ================================= 45 | MENU 46 | ================================= 47 | [0] Load host from external file 48 | [1] Add a new host 49 | [2] Print selected hosts 50 | [3] Check active hosts 51 | [4] Select only active hosts 52 | [5] Select bots 53 | [6] Execute command locally 54 | [7] Execute command on bots 55 | [8] Run external script 56 | [9] Open shell in a host 57 | [10] Exit 58 | >>> 59 | ``` 60 | -------------------------------------------------------------------------------- /botnet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcorosa/CnC-Botnet-in-Python/2ee458d2572c96cc242b70b76a6a509d6b64e332/botnet/__init__.py -------------------------------------------------------------------------------- /botnet/fabfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import fabric.colors as fab_col 4 | import getpass 5 | import paramiko 6 | from fabric.api import (env, run, sudo, execute, local, settings, hide, 7 | open_shell, parallel, serial, put) 8 | from fabric.contrib.console import confirm 9 | from fabric.decorators import hosts 10 | from tabulate import tabulate 11 | 12 | 13 | file_hosts = 'hosts.txt' 14 | paramiko.util.log_to_file('paramiko.log') 15 | env.colorize_errors = True 16 | # The selected hosts are the hosts in env (at the beginning) 17 | selected_hosts = env.hosts 18 | running_hosts = {} 19 | env.connection_attempts = 2 20 | # env.skip_bad_hosts = True 21 | 22 | 23 | def load_hosts(): 24 | """Load hosts from `hosts.txt` file. 25 | 26 | A host can either be in form 27 | username@host[:port] password 28 | or 29 | username@host[:port] 30 | If no port is specified, port 22 is selected. 31 | """ 32 | with open(file_hosts, 'r') as f: 33 | data = f.readlines() 34 | for line in data: 35 | try: 36 | host, password = line.strip().split() 37 | except ValueError: 38 | host = line.strip() 39 | password = None 40 | if len(host.split(':')) == 1: 41 | host = f'{host}:22' 42 | env.hosts.append(host) 43 | if password: 44 | env.passwords[host] = password.strip() 45 | env.hosts = list(set(env.hosts)) # Remove duplicates 46 | 47 | 48 | def add_host(): 49 | """Add a new host to the running hosts. The user can decide whether to add 50 | the host also to the external `hosts.txt` file. 51 | """ 52 | name = input('Username: ') 53 | host = input('Host: ') 54 | port = int(input('Port: ')) 55 | new_host = f'{name}@{host}:{port}' 56 | selected_hosts.append(new_host) 57 | password = None 58 | if confirm('Authenticate using a password? '): 59 | password = getpass.getpass('Password: ').strip() 60 | env.passwords[new_host] = password 61 | 62 | # Append the new host to the hosts file 63 | if confirm('Add the new host to the hosts file? '): 64 | if password: 65 | line = f'{new_host} {password}\n' 66 | else: 67 | line = f'{new_host}\n' 68 | with open(file_hosts, 'a') as f: 69 | f.write(line) 70 | 71 | 72 | def print_hosts(): 73 | """Print selected hosts. If hosts haven't been hand-selected yet, all hosts 74 | are selected. 75 | """ 76 | hosts = map(lambda x: [x, env.passwords.get(x, None)], selected_hosts) 77 | print(fab_col.green(tabulate(hosts, ['Host', 'Password']))) 78 | 79 | 80 | def check_hosts(): 81 | """Check if hosts are active or not and print the result.""" 82 | global running_hosts 83 | running_hosts = dict() 84 | for host in selected_hosts: 85 | print(fab_col.magenta("\nPing host %d of %d" % 86 | (selected_hosts.index(host) + 1, len(selected_hosts)))) 87 | response = os.system("ping -c 1 " + host.split("@")[1].split(":")[0]) 88 | if response == 0: 89 | running_hosts[host] = True 90 | else: 91 | running_hosts[host] = False 92 | # Convert running_hosts in order to print it as table 93 | mylist = map(lambda index: [index[0], str(index[1])], running_hosts.items()) 94 | print(fab_col.green(tabulate(mylist, ["Host", "Running"]))) 95 | 96 | 97 | def select_running_hosts(): 98 | """Select all active hosts.""" 99 | global selected_hosts 100 | with hide('stdout'): 101 | check_hosts() 102 | host_up = filter(lambda x: running_hosts.get(x, False), 103 | running_hosts.keys()) 104 | selected_hosts = host_up 105 | 106 | 107 | def choose_hosts(): 108 | """Select the hosts to be used.""" 109 | global selected_hosts 110 | mylist = map(lambda num, h: [num, h], enumerate(env.hosts)) 111 | print(fab_col.blue('Select Hosts (space-separated):')) 112 | print(fab_col.blue(tabulate(mylist, ['Number', 'Host']))) 113 | choices = input('> ').split() 114 | # Avoid letters in string index 115 | choices = filter(lambda x: x.isdigit(), choices) 116 | # Convert to int list 117 | choices = map(int, choices) 118 | # Avoid IndexError 119 | choices = filter(lambda x: x < len(env.hosts), choices) 120 | # Remove duplicates 121 | choices = list(set(choices)) 122 | # If no hosts are selected, keep the current hosts 123 | if len(choices) == 0: 124 | return 125 | # Get only selected hosts 126 | selected_hosts = map(lambda i: env.hosts[i], choices) 127 | 128 | 129 | def run_locally(cmd=None): 130 | """Execute a command locally.""" 131 | if not cmd: 132 | cmd = input('Insert command: ') 133 | with settings(warn_only=True): 134 | local(cmd) 135 | 136 | 137 | # This function cannot have the parallel decorator since 138 | # a sudo command must receive the user password 139 | @serial 140 | def _execute_sudo(command): 141 | """Execute a sudo command on a host and return the results of the 142 | execution. 143 | """ 144 | with settings(warn_only=True): 145 | return sudo(command[4:].strip(), shell=True) 146 | 147 | 148 | @parallel 149 | def _execute_command(command): 150 | """Execute a command on a host and return the results of the execution.""" 151 | with settings(warn_only=True): 152 | try: 153 | return run(command) 154 | except: 155 | print(fab_col.red(f'Error execution in host {env.host}')) 156 | return None 157 | 158 | 159 | @parallel 160 | def run_command(cmd=None): 161 | """Execute a command on hosts.""" 162 | if not cmd: 163 | cmd = input('Insert command: ') 164 | if cmd.strip()[:4] == 'sudo': 165 | execute(_execute_sudo, cmd, hosts=selected_hosts) 166 | else: 167 | execute(_execute_command, cmd, hosts=selected_hosts) 168 | 169 | 170 | @hosts(selected_hosts) 171 | def execute_script(): 172 | """Execute a script file.""" 173 | # Attention to script name. 174 | # Add security checks 175 | script_file = input('Name of the script: ') 176 | remote_path = '~/' 177 | if len(script_file) < 4 or '..' in script_file: 178 | # Invalid script 179 | print(fab_col.red('Error. Invalid script name.')) 180 | return 181 | 182 | for h in selected_hosts: 183 | with settings(host_string=h): 184 | with hide('running'): 185 | put(script_file, remote_path, mode=777) 186 | # Remove the path from the name of the script 187 | script_file = script_file.split('/')[-1] 188 | 189 | # Execution 190 | extension = script_file.split('.')[-1] 191 | if extension == script_file: 192 | print(fab_col.red('Invalid script')) 193 | return 194 | if extension == 'py': 195 | run_command(f'python {remote_path}{script_file}') 196 | elif extension == 'sh' or extension == 'bash': 197 | run_command(f'bash {remote_path}{script_file}') 198 | else: 199 | print(fab_col.red('Extension not supported')) 200 | 201 | # Delete the script 202 | with hide('running', 'stdout'): 203 | run_command(f'rm -f {remote_path}{script_file}') 204 | 205 | 206 | def open_sh(): 207 | """Open a shell on a host.""" 208 | mylist = map(lambda num, h: [num, h], enumerate(selected_hosts)) 209 | print(fab_col.blue(tabulate(mylist, ['Number', 'Host']))) 210 | try: 211 | n = int(input('Open shell in host number: ')) 212 | h = selected_hosts[n] 213 | execute(open_shell, host=h) 214 | except (NameError, IndexError): 215 | print(fab_col.red('Error: invalid host selection.')) 216 | print(fab_col.red('Shell not opened.')) 217 | -------------------------------------------------------------------------------- /botnet/utilities.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def choice_error(): 5 | """Print an error message in case the user selects a wrong action.""" 6 | print('Choice does not exist') 7 | 8 | 9 | def end(): 10 | """Terminate the execution.""" 11 | sys.exit(0) 12 | 13 | 14 | def menu(): 15 | """Print a menu with all the functionalities and return the user's choice 16 | """ 17 | print('=' * 33, '\nMENU\n', '=' * 33) 18 | descriptions = ['Load host from external file', 19 | 'Add a new host', 20 | 'Print selected hosts', 21 | 'Check active hosts', 22 | 'Select only active hosts', 23 | 'Select bots', 24 | 'Execute command locally', 25 | 'Execute command on bots', 26 | 'Run external script', 27 | 'Open shell in a host', 28 | 'Exit'] 29 | for num, func in enumerate(descriptions): 30 | print(f'[{num}] {func}') 31 | choice = input('>>> ') 32 | return choice 33 | -------------------------------------------------------------------------------- /hosts.txt.sample: -------------------------------------------------------------------------------- 1 | username@host:port password 2 | username@host:port 3 | username@host password 4 | username@host 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fabric; python_version < "3.9" 2 | Fabric39; python_version >= "3.9" 3 | tabulate 4 | -------------------------------------------------------------------------------- /start.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from botnet.fabfile import * 4 | from botnet.utilities import * 5 | 6 | 7 | if __name__ == '__main__': 8 | df = {'0': load_hosts, 9 | '1': add_host, 10 | '2': print_hosts, 11 | '3': check_hosts, 12 | '4': select_running_hosts, 13 | '5': choose_hosts, 14 | '6': run_locally, 15 | '7': run_command, 16 | '8': execute_script, 17 | '9': open_sh, 18 | '10': end} 19 | while True: 20 | choice = menu() 21 | df.get(choice, choice_error)() 22 | --------------------------------------------------------------------------------