nexmon – Rev 1

Subversion Repositories:
Rev:
#!/usr/bin/python

import sys
import os
import subprocess
import random
import time
import sqlite3
import threading
import hashlib
import gzip
import json
import datetime
import re

if sys.version_info[0] >= 3:
        from socketserver import ThreadingTCPServer
        from urllib.request import urlopen, URLError
        from urllib.parse import urlparse, parse_qs
        from http.client import HTTPConnection
        from http.server import SimpleHTTPRequestHandler
else:
        from SocketServer import ThreadingTCPServer
        from urllib2 import urlopen, URLError
        from urlparse import urlparse, parse_qs
        from httplib import HTTPConnection
        from SimpleHTTPServer import SimpleHTTPRequestHandler

        bytes = lambda a, b : a

port = 1337
url = None
cid = None
tls = threading.local()
nets = {}
cracker = None

class ServerHandler(SimpleHTTPRequestHandler):
        def do_GET(s):
                result = s.do_req(s.path)

                if not result:
                        return

                s.send_response(200)
                s.send_header("Content-type", "text/plain")
                s.end_headers()
                s.wfile.write(bytes(result, "UTF-8"))

        def do_POST(s):
                if ("dict" in s.path):
                        s.do_upload_dict()

                if ("cap" in s.path):
                        s.do_upload_cap()

                s.send_response(200)
                s.send_header("Content-type", "text/plain")
                s.end_headers()
                s.wfile.write(bytes("OK", "UTF-8"))

        def do_upload_dict(s):
                con = get_con()

                f = "dcrack-dict"
                c = f + ".gz"
                o = open(c, "wb")
                cl = int(s.headers['Content-Length'])
                o.write(s.rfile.read(cl))
                o.close()

                decompress(f)
        
                sha1 = hashlib.sha1()
                x = open(f, "rb")
                sha1.update(x.read())
                x.close()
                h = sha1.hexdigest()

                x = open(f, "rb")
                for i, l in enumerate(x):
                        pass

                i = i + 1
                x.close()

                n = "%s-%s.txt" % (f, h)
                os.rename(f, n)
                os.rename(c, "%s.gz" % n)

                c = con.cursor()
                c.execute("INSERT into dict values (?, ?, 0)", (h, i))
                con.commit()

        def do_upload_cap(s):
                cl = int(s.headers['Content-Length'])
                f = open("dcrack.cap.tmp.gz", "wb")
                f.write(s.rfile.read(cl))
                f.close()

                decompress("dcrack.cap.tmp")
                os.rename("dcrack.cap.tmp.gz", "dcrack.cap.gz")
                os.rename("dcrack.cap.tmp", "dcrack.cap")

        def do_req(s, path):
                con = get_con()

                c = con.cursor()

                c.execute("""DELETE from clients where 
                            (strftime('%s', datetime()) - strftime('%s', last))
                            > 300""")

                con.commit()

                if ("ping" in path):
                        return s.do_ping(path)

                if ("getwork" in path):
                        return s.do_getwork(path)

                if ("dict" in path and "status" in path):
                        return s.do_dict_status(path)

                if ("dict" in path and "set" in path):
                        return s.do_dict_set(path)

                if ("dict" in path):
                        return s.get_dict(path)

                if ("net" in path and "/crack" in path):
                        return s.do_crack(path)

                if ("net" in path and "result" in path):
                        return s.do_result(path)

                if ("cap" in path):
                        return s.get_cap(path)

                if ("status" in path):
                        return s.get_status()

                if ("remove" in path):
                        return s.remove(path)

                return "error"

        def remove(s, path):
                con = get_con()

                p = path.split("/")
                n = p[4].upper()

                c = con.cursor()
                c.execute("DELETE from nets where bssid = ?", (n,))
                con.commit()

                c.execute("DELETE from work where net = ?", (n,))
                con.commit()
                
                return "OK"

        def get_status(s):
                con = get_con()

                c = con.cursor()
                c.execute("SELECT * from clients")
        
                clients = []

                for r in c.fetchall():
                        clients.append(r['speed'])

                nets = []

                c.execute("SELECT * from dict where current = 1")
                dic = c.fetchone()

                c.execute("SELECT * from nets")

                for r in c.fetchall():
                        n = { "bssid" : r['bssid'] }
                        if r['pass']:
                                n["pass"] = r['pass']

                        if r['state'] != 2:
                                n["tot"] = dic["lines"]

                                did = 0
                                cur = con.cursor()
                                cur.execute("""SELECT * from work where net = ?
                                                and dict = ? and state = 2""",
                                                (n['bssid'], dic['id']))
                                for row in cur.fetchall():
                                        did += row['end'] - row['start']

                                n["did"] = did

                        nets.append(n)

                d = { "clients" : clients, "nets" : nets }

                return json.dumps(d)

        def do_result_pass(s, net, pw):
                con = get_con()

                pf = "dcrack-pass.txt"

                f = open(pf, "w")
                f.write(pw)
                f.write("\n")
                f.close()

                cmd = ["aircrack-ng", "-w", pf, "-b", net, "-q", "dcrack.cap"]
                p = subprocess.Popen(cmd, stdout=subprocess.PIPE, \
                        stdin=subprocess.PIPE)

                res = p.communicate()[0]
                res = str(res)

                os.remove(pf)

                if not "KEY FOUND" in res:
                        return "error"

                s.net_done(net)

                c = con.cursor()
                c.execute("UPDATE nets set pass = ? where bssid = ?", \
                        (pw, net))

                con.commit()

                return "OK"

        def net_done(s, net):
                con = get_con()

                c = con.cursor()
                c.execute("UPDATE nets set state = 2 where bssid = ?",
                        (net,))

                c.execute("DELETE from work where net = ?", (net,))
                con.commit()

        def do_result(s, path):
                con = get_con()

                p = path.split("/")
                n = p[4].upper()

                x  = urlparse(path)
                qs = parse_qs(x.query)

                if "pass" in qs:
                        return s.do_result_pass(n, qs['pass'][0])

                wl = qs['wl'][0]

                c = con.cursor()
                c.execute("SELECT * from nets where bssid = ?", (n,))
                r = c.fetchone()
                if r and r['state'] == 2:
                        return "Already done"

                c.execute("""UPDATE work set state = 2 where 
                        net = ? and dict = ? and start = ? and end = ?""",
                        (n, wl, qs['start'][0], qs['end'][0]))

                con.commit()

                if c.rowcount == 0:
                        c.execute("""INSERT into work values
                                (NULL, ?, ?, ?, ?, datetime(), 2)""",
                                        (n, wl, qs['start'][0], qs['end'][0]))
                        con.commit()

                # check status
                c.execute("""SELECT * from work where net = ? and dict = ?
                        and state = 2 order by start""", (n, wl))

                i = 0
                r = c.fetchall()
                for row in r:
                        if i == row['start']:
                                i = row['end']
                        else:
                                break

                c.execute("SELECT * from dict where id = ? and lines = ?",
                        (wl, i))

                r = c.fetchone()

                if r:
                        s.net_done(n)

                return "OK"

        def get_cap(s, path):
                return s.serve_file("dcrack.cap.gz")

        def get_dict(s, path):
                p = path.split("/")
                n = p[4]

                fn = "dcrack-dict-%s.txt.gz" % n

                return s.serve_file(fn)

        def serve_file(s, fn):
                s.send_response(200)
                s.send_header("Content-type", "application/x-gzip")
                s.end_headers()

                # XXX openat
                f = open(fn, "rb")
                s.wfile.write(f.read())
                f.close()

                return None

        def do_crack(s, path):
                con = get_con()

                p = path.split("/")

                n = p[4].upper()

                c = con.cursor()
                c.execute("INSERT into nets values (?, NULL, 1)", (n,))
                con.commit()

                return "OK"

        def do_dict_set(s, path):
                con = get_con()

                p = path.split("/")

                h = p[4]

                c = con.cursor()
                c.execute("UPDATE dict set current = 0")
                c.execute("UPDATE dict set current = 1 where id = ?", (h,))
                con.commit()

                return "OK"

        def do_ping(s, path):
                con = get_con()

                p = path.split("/")

                cid = p[4]

                x  = urlparse(path)
                qs = parse_qs(x.query)

                speed = qs['speed'][0]

                c = con.cursor()
                c.execute("SELECT * from clients where id = ?", (cid,))
                r = c.fetchall()
                if (not r):
                        c.execute("INSERT into clients values (?, ?, datetime())",
                                  (cid, int(speed)))
                else:
                        c.execute("""UPDATE clients set speed = ?, 
                                        last = datetime() where id = ?""",
                                        (int(speed), cid))

                con.commit()

                return "60"

        def try_network(s, net, d):
                con = get_con()

                c = con.cursor()
                c.execute("""SELECT * from work where net = ? and dict = ?
                                order by start""", (net['bssid'], d['id']))

                r = c.fetchall()

                s     = 5000000
                i     = 0
                found = False

                for row in r:
                        if found:
                                if i + s > row['start']:
                                        s = row['start'] - i
                                break

                        if (i >= row['start'] and i <= row['end']):
                                i = row['end']
                        else:
                                found = True

                if i + s > d['lines']:
                        s = d['lines'] - i

                if s == 0:
                        return None

                c.execute("INSERT into work values (NULL, ?, ?, ?, ?, datetime(), 1)",
                        (net['bssid'], d['id'], i, i + s))

                con.commit()

                crack = { "net"   : net['bssid'], \
                          "dict"  : d['id'], \
                          "start" : i, \
                          "end"   : i + s }

                j = json.dumps(crack)

                return j

        def do_getwork(s, path):
                con = get_con()

                c = con.cursor()

                c.execute("""DELETE from work where 
                            ((strftime('%s', datetime()) - strftime('%s', last))
                            > 3600) and state = 1""")

                con.commit()

                c.execute("SELECT * from dict where current = 1")
                d = c.fetchone()

                c.execute("SELECT * from nets where state = 1")
                r = c.fetchall()

                for row in r:
                        res = s.try_network(row, d)
                        if res:
                                return res

                # try some old stuff
                c.execute("""select * from work where state = 1 
                        order by last limit 1""")

                res = c.fetchone()

                if res:
                        c.execute("DELETE from work where id = ?", (res['id'],))
                        for row in r:
                                res = s.try_network(row, d)
                                if res:
                                        return res

                res = { "interval" : "60" }

                return json.dumps(res)

        def do_dict_status(s, path):
                p = path.split("/")

                d = p[4]

                try:
                        f = open("dcrack-dict-%s.txt" % d)
                        f.close()

                        return "OK"
                except:
                        return "NO"

def create_db():
        con = get_con()

        c = con.cursor()
        c.execute("""create table clients (id varchar(255),
                        speed integer, last datetime)""")

        c.execute("""create table dict (id varchar(255), lines integer,
                        current boolean)""")
        c.execute("""create table nets (bssid varchar(255), pass varchar(255),
                        state integer)""")

        c.execute("""create table work (id integer primary key,
                net varchar(255), dict varchar(255),
                start integer, end integer, last datetime, state integer)""")

def connect_db():
        con = sqlite3.connect('dcrack.db')
        con.row_factory = sqlite3.Row

        return con

def get_con():
        global tls

        try:
                return tls.con
        except:
                tls.con = connect_db()
                return tls.con

def init_db():
        con = get_con()
        c = con.cursor()

        try:
                c.execute("SELECT * from clients")
        except:
                create_db()

def server():
        init_db()

        server_class = ThreadingTCPServer 
        httpd = server_class(('', port), ServerHandler)

        print("Starting server")
        httpd.serve_forever()

def usage():
        print("""dcrack v0.3

        Usage: dcrack.py [MODE]
        server                        Runs coordinator
        client <server addr>          Runs cracker
        cmd    <server addr> [CMD]    Sends a command to server

                [CMD] can be:
                        dict   <file>
                        cap    <file>
                        crack  <bssid>
                        remove <bssid>
                        status""")
        exit(1)

def get_speed():
        print("Getting speed")
        p = subprocess.Popen(["aircrack-ng", "-S"], stdout=subprocess.PIPE)
        speed = p.stdout.readline()
        speed = speed.split()
        speed = speed[len(speed) - 2]
        return int(speed)

def get_cid():
        return random.getrandbits(64)

def do_ping(speed):
        global url, cid

        u = url + "client/" + str(cid) + "/ping?speed=" + str(speed)
        stuff = urlopen(u).read()
        interval = int(stuff)

        return interval

def pinger(speed):
        while True:
                interval = try_ping(speed)
                time.sleep(interval)

def try_ping(speed):
        while True:
                try:
                        return do_ping(speed)
                except URLError:
                        print("Conn refused (pinger)")
                        time.sleep(60)

def get_work():
        global url, cid, cracker

        u = url + "client/" + str(cid) + "/getwork"
        stuff = urlopen(u).read()
        stuff = stuff.decode("utf-8")

        crack = json.loads(stuff)

        if "interval" in crack:
                print("Waiting")
                return int(crack['interval'])

        wl  = setup_dict(crack)
        cap = get_cap(crack)

        print("Cracking")

        cmd = ["aircrack-ng", "-w", wl, "-b", crack['net'], "-q", cap]

        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, \
                stdin=subprocess.PIPE)

        cracker = p

        res = p.communicate()[0]
        res = str(res)

        cracker = None

        if ("not in dictionary" in res):
                print("No luck")
                u = "%snet/%s/result?wl=%s&start=%d&end=%d&found=0" % \
                        (url, crack['net'], crack['dict'], \
                        crack['start'], crack['end'])

                stuff = urlopen(u).read()
        elif "KEY FOUND" in res:
                pw = re.sub("^.*\[ ", "", res)

                i = pw.rfind(" ]")
                if i == -1:
                        raise BaseException("Can't parse output")

                pw = pw[:i]

                print("Key for %s is %s" % (crack['net'], pw))

                u = "%snet/%s/result?pass=%s" % (url, crack['net'], pw)
                stuff = urlopen(u).read()

        return 0

def decompress(fn):
        f = gzip.open(fn + ".gz")
        o = open(fn, "wb")
        o.writelines(f)
        o.close()
        f.close()

def setup_dict(crack):
        global url

        d = crack['dict']

        fn = "dcrack-client-dict-%s.txt" % d

        try:
                f = open(fn)
                f.close()
        except:
                print("Downloading dictionary %s" % d)

                u = "%sdict/%s" % (url, d)
                stuff = urlopen(u)

                f = open(fn + ".gz", "wb")
                f.write(stuff.read())
                f.close()

                print("Uncompressing dictionary")
                decompress(fn)
        
                sha1 = hashlib.sha1()
                f = open(fn, "rb")
                sha1.update(f.read())
                f.close()
                h = sha1.hexdigest()

                if h != d:
                        print("bad dictionary")
                        exit(1)

        s = "dcrack-client-dict-%s-%d:%d.txt" \
                % (d, crack['start'], crack['end']) 

        try:
                f = open(s)
                f.close()
        except:
                print("Splitting dict %s" % s)
                f = open(fn, "rb")
                o = open(s, "wb")

                for i, l in enumerate(f):
                        if i >= crack['end']:
                                break

                        if i >= crack['start']:
                                o.write(l)

                f.close()
                o.close()

        return s

def get_cap(crack):
        global url, nets

        fn = "dcrack-client.cap"

        bssid = crack['net'].upper()

        if bssid in nets:
                return fn

        try:
                f = open(fn, "rb")
                f.close()
                check_cap(fn, bssid)
        except:
                pass

        if bssid in nets:
                return fn

        print("Downloading cap")
        u = "%scap/%s" % (url, bssid)

        stuff = urlopen(u)

        f = open(fn + ".gz", "wb")
        f.write(stuff.read())
        f.close()

        print("Uncompressing cap")
        decompress(fn)

        nets = {}
        check_cap(fn, bssid)

        if bssid not in nets:
                raise BaseException("Can't find net %s" % bssid)

        return fn

def process_cap(fn):
        global nets

        nets = {}

        print("Processing cap")
        p = subprocess.Popen(["aircrack-ng", fn], stdout=subprocess.PIPE, \
                stdin=subprocess.PIPE)
        found = False
        while True:
                line = p.stdout.readline()

                try:
                        line = line.decode("utf-8")
                except:
                        line = str(line)

                if "1 handshake" in line:
                        found = True
                        parts = line.split()
                        b = parts[1].upper()
#                       print("BSSID [%s]" % b)
                        nets[b] = True

                if (found and line == "\n"):
                        break

        p.stdin.write(bytes("1\n", "utf-8"))
        p.communicate()

def check_cap(fn, bssid):
        global nets

        cmd = ["aircrack-ng", "-b", bssid, fn]
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE)

        res = p.communicate()[0]
        res = str(res)

        if "No matching network found" not in res:
                nets[bssid] = True

def worker():
        while True:
                interval = get_work()
                time.sleep(interval)

def set_url():
        global url, port

        if len(sys.argv) < 3:
                print("Provide server addr")
                usage()

        host = sys.argv[2]

        if not ":" in host:
                host = "%s:%d" % (host, port)

        url = "http://" + host + "/" + "dcrack/"

def client():
        global cid, cracker, url

        set_url()
        url += "worker/"

        speed = get_speed()
        print("Speed", speed)

        cid = get_cid()

        print("CID", cid)

        try_ping(speed)
        t = threading.Thread(target=pinger, args=(speed,))
        t.start()

        while True:
                try:
                        do_client()
                        break
                except URLError:
                        print("Conn refused")
                        time.sleep(60)

def do_client():
        try:
                worker()
        except KeyboardInterrupt:
                if cracker:
                        cracker.kill()
                print("one more time...")

def upload_file(url, f):
        x  = urlparse(url)
        c = HTTPConnection(x.netloc)

        # XXX not quite HTTP form

        f = open(f, "rb")
        c.request("POST", x.path, f)
        res = c.getresponse()
        stuff = res.read()
        c.close()
        f.close()

        return stuff

def compress_file(f):
        i = open(f, "rb")
        o = gzip.open(f + ".gz", "wb")
        o.writelines(i)
        o.close()
        i.close()

def send_dict():
        global url

        if len(sys.argv) < 5:
                print("Need dict")
                usage()

        d = sys.argv[4]

        print("Calculating dictionary hash for %s" % d)

        sha1 = hashlib.sha1()
        f = open(d, "rb")
        sha1.update(f.read())
        f.close()

        h = sha1.hexdigest()

        print("Hash is %s" % h)

        u = url + "dict/" + h + "/status"
        stuff = urlopen(u).read()

        if "NO" in str(stuff):
                u = url + "dict/create"
                print("Compressing dictionary")
                compress_file(d)
                print("Uploading dictionary")
                upload_file(u, d + ".gz")

        print("Setting dictionary to %s" % d)
        u = url + "dict/" + h + "/set"
        stuff = urlopen(u).read()

def send_cap():
        global url

        if len(sys.argv) < 5:
                print("Need cap")
                usage()

        cap = sys.argv[4]

        print("Cleaning cap %s" % cap)
        subprocess.Popen(["wpaclean", cap + ".clean", cap], \
           stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]

        print("Compressing cap")
        compress_file(cap + ".clean")

        u = url + "cap/create"
        upload_file(u, cap + ".clean.gz")

def cmd_crack():
        net_cmd("crack")

def net_cmd(op):
        global url

        if len(sys.argv) < 5:
                print("Need BSSID")
                usage()

        bssid = sys.argv[4]

        print("%s %s" % (op, bssid))
        u = "%snet/%s/%s" % (url, bssid, op)
        stuff = urlopen(u).read()

def cmd_remove():
        net_cmd("remove")

def cmd_status():
        u = "%sstatus" % url
        stuff = urlopen(u).read()

        stuff = json.loads(stuff.decode("utf-8"))

#       print(stuff)
#       print("=============")

        i = 0
        speed = 0
        for c in stuff['clients']:
                i += 1
                speed += c

        print("Clients\t%d\nSpeed\t%d\n" % (i, speed))

        need = 0

        for n in stuff['nets']:
                out = n['bssid'] + " "

                if "pass" in n:
                        out += n['pass']
                elif "did" in n:
                        did = int(float(n['did']) / float(n['tot']) * 100.0)
                        out += str(did) + "%"
                        need += n['tot'] - n['did']
                else:
                        out += "-"

                print(out)

        if need != 0:
                print("\nKeys left %d" % need)
                if speed != 0:
                        s = int(float(need) / float(speed))
                        sec = datetime.timedelta(seconds=s)
                        d = datetime.datetime(1,1,1) + sec
                        print("ETA %dh %dm" % (d.hour, d.minute))

def do_cmd():
        global url

        set_url()
        url += "cmd/"

        if len(sys.argv) < 4:
                print("Need CMD")
                usage()

        cmd = sys.argv[3]

        if "dict" in cmd:
                send_dict()
        elif "cap" in cmd:
                send_cap()
        elif "crack" in cmd:
                cmd_crack()
        elif "status" in cmd:
                cmd_status()
        elif "remove" in cmd:
                cmd_remove()
        else:
                print("Unknown cmd %s" % cmd)
                usage()

def main():
        if len(sys.argv) < 2:
                usage()

        cmd = sys.argv[1]

        if cmd == "server":
                server()
        elif cmd == "client":
                client()
        elif cmd == "cmd":
                do_cmd()
        else:
                print("Unknown cmd", cmd)
                usage()

        exit(0)

if __name__ == "__main__":
        main()