From 6f61609f80e92ab18bc6ae2b852e3ee1ce256ae6 Mon Sep 17 00:00:00 2001 From: anoduck <11767-anoduck@users.noreply.gitgud.io> Date: Thu, 12 Oct 2023 04:57:15 -0400 Subject: [PATCH] Working on classes --- ctiger.py | 213 ++++++++++++----------------- ctiger_study.ipynb | 197 +++++++++++++++++++++++++++ printer.py | 36 +++++ scapy_ex.py | 331 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 652 insertions(+), 125 deletions(-) create mode 100644 printer.py create mode 100644 scapy_ex.py diff --git a/ctiger.py b/ctiger.py index bb8751c..2cd9994 100644 --- a/ctiger.py +++ b/ctiger.py @@ -11,9 +11,6 @@ import os import sys import fcntl import argparse -from scapy.all import * -from scapy.packet import Packet -from scapy.plist import PacketList from scapy.sendrecv import sniff from scapy.sendrecv import sendp from scapy.layers.dot11 import Dot11Beacon @@ -23,21 +20,19 @@ from scapy.layers.dot11 import RadioTap from scapy.layers.dot11 import Dot11Deauth from scapy.layers.dot11 import Dot11FCS from scapy.layers.eap import EAPOL +import scapy_ex from art.art import tprint import multiprocessing as mp import asyncio -import threading +import threading from random import choice -from alive_progress import alive_it from configobj import ConfigObj, validate from collections import Counter import pandas as pd import socket import signal import logging -from rich.logging import RichHandler from time import sleep -from daemonize import Daemonize sys.path.append(os.path.expanduser('~/.local/lib/python3.11/site-packages')) @@ -50,7 +45,8 @@ config_file = os.path.abspath("/etc/ctiger/config.ini") pc = Counter() logging.getLogger("scapy.runtime").setLevel(logging.ERROR) -scapy.config.Conf.layers.filter([Dot11, Dot11Beacon, Dot11Elt, RadioTap, Dot11Deauth, Dot11FCS, EAPOL]) +# scapy.config.Conf.layers.filter([Dot11, Dot11Beacon, Dot11Elt, +# RadioTap, Dot11Deauth, Dot11FCS, EAPOL]) # ------------------------------------------------------------ # ██████╗███████╗ ██████╗ ███████╗██████╗ ███████╗ ██████╗ @@ -277,6 +273,20 @@ def stop_monitor(if_mon): return False +# ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗ +# ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝ +# ██████╔╝██║ ██║██████╔╝██║ ███╗█████╗ +# ██╔═══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝ +# ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗ +# ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ +# ------------------------------------------- +class Purge: + def __init__(self, mon_dev, mon_type, mac_targ, log): + self.mon_dev = mon_dev + self.mon_if = start_monitor(self.mon_dev, mon_type, mac_targ) + self.scan_df = get_df() + self.log = log + # ------------------------------------------------------------------ # ███████╗████████╗██████╗ █████╗ ██╗███╗ ██╗███████╗██████╗ # ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║██╔════╝██╔══██╗ @@ -285,55 +295,37 @@ def stop_monitor(if_mon): # ███████║ ██║ ██║ ██║██║ ██║██║██║ ╚████║███████╗██║ ██║ # ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ # ---------------------------------------------------------------- -def strainer(pkt): - if pkt[Dot11].type == 0 and pkt[Dot11].subtype == 4: - bssid = pkt[Dot11FCS].addr2 - log.info('BSSID for sieve: ' + str(bssid)) - new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11, - addr1=bssid, - addr2=macaddr, - ID=65535) - log.debug('Sending CTS frame to ' + str(bssid) + ' with type 11') - sendp(new_pkt, iface=mon_if, verbose=0, count=2) - if pkt[Dot11].type == 1 and pkt[Dot11].subtype == 12: - try: + def strainer(pkt, log): + if pkt[Dot11].type == 0 and pkt[Dot11].subtype == 4: bssid = pkt[Dot11FCS].addr2 - except: - bssid = pkt[Dot11].addr2 - dbm_signal = pkt.dBm_AntSignal - if pkt.haslayer(Dot11Beacon): - stats = pkt[Dot11Beacon].network_stats() - channel = stats.get('channel') - else: - channel = extract_channel(pkt[Dot11Elt]) - scan_df.loc[bssid] = ['N/A', dbm_signal, channel, 'N/A'] - - -# ----------------------------------------------------------------- -# ███████╗██╗ ██╗ ██████╗ ██████╗ ████████╗███████╗██████╗ -# ██╔════╝██║ ██║██╔═══██╗██╔═══██╗╚══██╔══╝██╔════╝██╔══██╗ -# ███████╗███████║██║ ██║██║ ██║ ██║ █████╗ ██████╔╝ -# ╚════██║██╔══██║██║ ██║██║ ██║ ██║ ██╔══╝ ██╔══██╗ -# ███████║██║ ██║╚██████╔╝╚██████╔╝ ██║ ███████╗██║ ██║ -# ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ -# ----------------------------------------------------------------- -def shooter(mon_if, bssid): - log.info('Shooter Running') - new_pkt = RadioTap()/Dot11(type=1, subtype=12, - addr1="ff:ff:ff:ff:ff:ff", - addr2=bssid, addr3=bssid) - log.debug('Sending CTS frame to ' + str(bssid) + ' with type 11') - sendp(new_pkt, iface=mon_if, verbose=0, inter=0.1, count=2, realtime=True) - - -def df_writer(scan_df, valid_file): - while True: - if scan_df.empty: - pass - else: - scan_df.to_csv(valid_file) - print('results written to file ' + valid_file) + log.info('BSSID for sieve: ' + str(bssid)) + new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11, + addr1=bssid, + addr2=macaddr, + ID=65535) + log.debug('Sending CTS frame to ' + str(bssid) + ' with type 11') + sendp(new_pkt, iface=mon_if, verbose=0, count=1) + if pkt[Dot11].type == 1 and pkt[Dot11].subtype == 12: + try: + bssid = pkt[Dot11FCS].addr2 + except: + bssid = pkt[Dot11].addr2 + dbm_signal = pkt.dBm_AntSignal + # if pkt.haslayer(Dot11Beacon): + # stats = pkt[Dot11Beacon].network_stats() + # channel = stats.get('channel') + # else: + # channel = extract_channel(pkt[Dot11]) + channel = pkt[Dot11].channel() or pkt[RadioTap].Channel + scan_df.loc[bssid] = ['N/A', dbm_signal, channel, 'N/A'] + def df_writer(scan_df, valid_file): + while True: + if scan_df.empty: + pass + else: + scan_df.to_csv(valid_file) + print('results written to file ' + valid_file) # --------------------------------------------------------------------------- # ██████╗██╗ ██╗ ██████╗ ██╗ ██╗███╗ ██╗███╗ ██╗███████╗██████╗ @@ -343,53 +335,26 @@ def df_writer(scan_df, valid_file): # ╚██████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║██║ ╚████║███████╗██║ ██║ # ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ # ---------------------------------------------------------------------------- -def channel_runner(mon_if, channels): - log.info('Channel Runner NG started.') - log.info('Preliminary channel list: ' + str(channels)) - log.debug('Preliminary list type is: ' + str(type(channels))) - chanlist = channels.split(',') - chlist = list(set(chanlist)) - log.info('Channel list: ' + str(chlist)) - chans = [int(chan) for chan in chlist] - thread = threading.current_thread() - print(f'name={thread.name}, daemon={thread.daemon}') - while True: - ichan = choice(chans) - os.system('iw dev ' + mon_if + ' set channel ' + str(ichan)) - # log.debug('Channel set to ' + str(ichan)) - sleep(14.7) - - -def signal_handler(signal, frame): - print('You pressed Ctrl+C!') - log.info('Shutting down') - sys.exit(0) - - -def sieve(pkt): - try: - bssid = pkt[Dot11FCS].addr2 - except: - bssid = pkt[Dot11].addr2 - if bssid is not None: - log.info('BSSID for sieve: ' + str(bssid)) - new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11, - addr1=bssid, - addr2=macaddr, - ID=65535)/Dot11FCS() - log.debug('Sending CTS frame to ' + str(bssid) + ' with type 11') - sendp(new_pkt, iface=mon_if, verbose=0, count=1) - # return bssid - - -def thumper(pkt): - try: - bssid = pkt[Dot11FCS].addr2 - except: - bssid = pkt[Dot11].addr2 - if bssid is not None: - return bssid + def channel_runner(mon_if, channels, log): + log.info('Channel Runner NG started.') + log.info('Preliminary channel list: ' + str(channels)) + log.debug('Preliminary list type is: ' + str(type(channels))) + chanlist = channels.split(',') + chlist = list(set(chanlist)) + log.info('Channel list: ' + str(chlist)) + chans = [int(chan) for chan in chlist] + thread = threading.current_thread() + print(f'name={thread.name}, daemon={thread.daemon}') + while True: + ichan = choice(chans) + os.system('iw dev ' + mon_if + ' set channel ' + str(ichan)) + # log.debug('Channel set to ' + str(ichan)) + sleep(14.7) + def signal_handler(signal, frame, log): + print('You pressed Ctrl+C!') + log.info('Shutting down') + sys.exit(0) # ----------------------------------------------------------------------------- # ███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗ @@ -399,26 +364,24 @@ def thumper(pkt): # ██║ ╚═╝ ██║██║ ██║╚██████╗ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗ # ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ # ---------------------------------------------------------------------------- -async def mac_purge(mon_dev, mon_type, valid_file, channels, mac_targ): - signal.signal(signal.SIGINT, signal_handler) - print('Enter Ctrl+C TWICE to fully stop the script.') - global mon_if - mon_if = start_monitor(mon_dev, mon_type, mac_targ) - log.info(f'You now have {mon_if}.') - chop = asyncio.to_thread(channel_runner, mon_dev, channels) - chopper = asyncio.create_task(chop) - log.info('Channel runner started.') - await asyncio.sleep(0) - while True: - log.info('starting sniffer') - asniff = AsyncSniffer(iface=mon_if, prn=strainer, - store=False, monitor=True) - asniff.start() - log.info('asniffer started') - nloop = asyncio.get_running_loop() - nloop.add_signal_handler(0, df_writer, scan_df, valid_file) - forever_wait = threading.Event() - forever_wait.wait() + async def mac_purge(self, mon_dev, valid_file, channels, log): + signal.signal(signal.SIGINT, Purge.signal_handler) + print('Enter Ctrl+C TWICE to fully stop the script.') + chop = asyncio.to_thread(Purge.channel_runner, mon_dev, channels) + chopper = asyncio.create_task(chop) + log.info('Channel runner started.') + await asyncio.sleep(0) + while True: + log.info('starting sniffer') + asniff = asyncio.AsyncSniffer(iface=self.mon_if, prn=self.strainer, + store=False, monitor=True) + asniff.start() + log.info('asniffer started') + nloop = asyncio.get_running_loop() + nloop.add_signal_handler(0, self.df_writer, + self.scan_df, valid_file) + forever_wait = threading.Event() + forever_wait.wait() # ----------------------------------------------------------------------------- @@ -695,9 +658,9 @@ def process_args(args: argparse.Namespace, config): case "mac": log.info('Beginning Mac Purge') mac_targ = 'unique' - asyncio.run(mac_purge(args.interface, args.if_type, - args.valid_file, args.channels, - mac_targ)) + asyncio.run(Purge.mac_purge(args.interface, args.if_type, + args.valid_file, args.channels, + mac_targ)) case "scn": log.info('Start scanning...') if not args.save_results: @@ -836,7 +799,7 @@ else: mac_parse.add_argument('-c', '--chans', dest='channels', default=config['MAC_PURGE']['channel_list'], help='A single or comma seperated list of channels.') - mac_parse.set_defaults(fun=mac_purge) + mac_parse.set_defaults(fun=Purge.mac_purge) # scn Subcommands scn_parse = subparse.add_parser('scn', help='Scan for target') diff --git a/ctiger_study.ipynb b/ctiger_study.ipynb index 6e84d0a..e27facb 100644 --- a/ctiger_study.ipynb +++ b/ctiger_study.ipynb @@ -1698,6 +1698,203 @@ "# Close the client socket\n", "client_socket.close()" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scapy Examples from popular programs (Circa 2018)\n", + "\n", + "Below are examples of popular scapy functions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 1. Aggr-Inject" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def __init__(self, recv_mac, trans_mac, dst_mac):\n", + " self.rt = RadioTap(len=18, present='Flags+Rate+Channel+dBm_AntSignal+Antenna', notdecoded='\\x00\\x6c' + get_frequency(CHANNEL) + '\\xc0\\x00\\xc0\\x01\\x00\\x00')\n", + " self.dot11hdr = Dot11(type=\"Data\", subtype=DOT11_SUBTYPE_DATA, addr1=recv_mac, addr2=trans_mac, addr3=dst_mac, SC=0x3060, FCfield=0x01)\n", + " self.data = self.rt / self.dot11hdr\n", + " self.recv_mac = recv_mac\n", + " self.trans_mac = trans_mac\n", + " self.dst_mac = dst_mac\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2. Aggr-Inject" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " def __init__(self, recv_mac, src_mac, dst_mac, ds=0x01):\n", + " self.rt = RadioTap(len=18, present='Flags+Rate+Channel+dBm_AntSignal+Antenna', notdecoded='\\x00\\x6c' + get_frequency(CHANNEL) + '\\xc0\\x00\\xc0\\x01\\x00\\x00')\n", + " self.dot11hdr = Dot11(type=\"Data\", subtype=DOT11_SUBTYPE_QOS_DATA, addr1=recv_mac, addr2=src_mac, addr3=dst_mac, SC=0x3060, FCfield=ds) / Raw(\"\\x80\\x00\")\n", + " self.data = self.rt / self.dot11hdr\n", + " self.num_subframes = 0\n", + " self.recv_mac = recv_mac\n", + " self.src_mac = src_mac\n", + " self.dst_mac = dst_mac" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 3. Aggr-Inject" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + " def __init__(self, recv_mac, src_mac, dst_mac, ds=0x01):\n", + " self.rt = RadioTap(len=18, present='Flags+Rate+Channel+dBm_AntSignal+Antenna', notdecoded='\\x00\\x6c' + get_frequency(CHANNEL) + '\\xc0\\x00\\xc0\\x01\\x00\\x00')\n", + " self.dot11hdr = Dot11(type=\"Data\", subtype=DOT11_SUBTYPE_QOS_DATA, addr1=recv_mac, addr2=src_mac, addr3=dst_mac, SC=0x3060, FCfield=ds) / Raw(\"\\x00\\x00\")\n", + " self.data = self.rt\n", + " self.num_subframes = 0\n", + " self.recv_mac = recv_mac\n", + " self.src_mac = src_mac\n", + " self.dst_mac = dst_mac\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 4. Pyrit" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# May result in 'anonymous' AP\n", + " self._add_ap(ap_mac, dot11_pckt)\n", + " ap = self.air[ap_mac]\n", + "\n", + " self._add_station(ap, sta_mac)\n", + " sta = ap[sta_mac]\n", + "\n", + " if EAPOL_WPAKey in dot11_pckt:\n", + " wpakey_pckt = dot11_pckt[EAPOL_WPAKey]\n", + " elif EAPOL_RSNKey in dot11_pckt:\n", + " wpakey_pckt = dot11_pckt[EAPOL_RSNKey]\n", + " elif dot11_pckt.isFlagSet('type', 'Data') \\\n", + " and dot11_pckt.haslayer(scapy.layers.dot11.Dot11WEP):\n", + " # An encrypted data packet - maybe useful for CCMP-attack\n", + "\n", + " dot11_wep = str(dot11_pckt[scapy.layers.dot11.Dot11WEP])\n", + " # Ignore packets which has less than len(header + data + signature)\n", + " if len(dot11_wep) < 8 + 6 + 8:\n", + " return\n", + "\n", + " # Ignore packets with high CCMP-counter. A high CCMP-counter\n", + " # means that we missed a lot of packets since the last\n", + " # authentication which also means a whole new authentication\n", + " # might already have happened.\n", + " ccmp_counter = (dot11_wep[0:2] + dot11_wep[4:8])[::-1]\n", + " if int(binascii.hexlify(ccmp_counter), 16) < 30:\n", + " self._add_ccmppckt(sta, pckt)\n", + " return\n", + " else:\n", + " return\n", + "\n", + " # Frame 1: pairwise set, install unset, ack set, mic unset\n", + " # results in ANonce\n", + " if wpakey_pckt.areFlagsSet('KeyInfo', ('pairwise', 'ack')) \\\n", + " and wpakey_pckt.areFlagsNotSet('KeyInfo', ('install', 'mic')):\n", + " self._add_keypckt(sta, 0, pckt)\n", + " return\n", + "\n", + " # Frame 2: pairwise set, install unset, ack unset, mic set,\n", + " # SNonce != 0. Results in SNonce, MIC and keymic_frame\n", + " elif wpakey_pckt.areFlagsSet('KeyInfo', ('pairwise', 'mic')) \\\n", + " and wpakey_pckt.areFlagsNotSet('KeyInfo', ('install', 'ack')) \\\n", + " and not all(c == '\\x00' for c in wpakey_pckt.Nonce):\n", + " self._add_keypckt(sta, 1, pckt)\n", + " return\n", + "\n", + " # Frame 3: pairwise set, install set, ack set, mic set\n", + " # Results in ANonce\n", + " elif wpakey_pckt.areFlagsSet('KeyInfo', ('pairwise', 'install', \\\n", + " 'ack', 'mic')):\n", + " self._add_keypckt(sta, 2, pckt)\n", + " return\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 5. Aggr-Inject" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def ssid_packet():\n", + " ap_mac = '00:00:00:00:00:00'\n", + " rt = RadioTap(len=18, present='Flags+Rate+Channel+dBm_AntSignal+Antenna', notdecoded='\\x00\\x6c' + get_frequency(CHANNEL) + '\\xc0\\x00\\xc0\\x01\\x00\\x00')\n", + " beacon_packet = Dot11(subtype=8, addr1='ff:ff:ff:ff:ff:ff', addr2=ap_mac, addr3=ap_mac) \\\n", + " / Dot11Beacon(cap=0x2105) \\\n", + " / Dot11Elt(ID='SSID', info=\"injected SSID\") \\\n", + " / Dot11Elt(ID='Rates', info=AP_RATES) \\\n", + " / Dot11Elt(ID='DSset', info=chr(CHANNEL))\n", + "\n", + " # Update sequence number\n", + " beacon_packet.SC = 0x3060\n", + "\n", + " # Update timestamp\n", + " beacon_packet[Dot11Beacon].timestamp = time.time()\n", + "\n", + " mpdu_len = len(beacon_packet) + 4\n", + "\n", + " if mpdu_len % 4 != 0:\n", + " padding = \"\\x00\" * (4 - (mpdu_len % 4)) # Align to 4 octets\n", + " else:\n", + " padding = \"\"\n", + " mpdu_len <<= 4\n", + " crc_fun = crcmod.mkCrcFun(0b100000111, rev=True, initCrc=0x00, xorOut=0xFF)\n", + "\n", + " crc = crc_fun(struct.pack('> 4, crc, delim_sig))\n", + " #hexdump(maccrc)\n", + " ampdu_header = struct.pack('= verbose_level: + sys.stdout.write(message) + sys.stdout.write('\n') + + @staticmethod + def write(message): + """Write a message to stdout""" + sys.stdout.write(message) + sys.stdout.write('\n') + + @staticmethod + def error(message): + """Write a message to stderr""" + sys.stderr.write(message) + sys.stderr.write('\n') + + @staticmethod + def exception(e): + """Write a summary of an exception with a stack trace""" + Printer.error(repr(e)) + traceback.print_exc(file=sys.stderr) + + diff --git a/scapy_ex.py b/scapy_ex.py new file mode 100644 index 0000000..e22da8b --- /dev/null +++ b/scapy_ex.py @@ -0,0 +1,331 @@ +""" +A set of additions and modifications to scapy to assist in parsing dot11 +""" +import scapy + +from scapy.fields import BitField +from scapy.fields import ByteField +from scapy.fields import ConditionalField +from scapy.fields import EnumField +from scapy.fields import Field +from scapy.fields import FieldLenField +from scapy.fields import FieldListField +from scapy.fields import FlagsField +from scapy.fields import LEFieldLenField +from scapy.fields import LELongField +from scapy.fields import LEShortField +from scapy.fields import StrFixedLenField +from scapy.layers.dot11 import Dot11Elt +from scapy.layers.dot11 import Dot11ProbeReq +from scapy.packet import Packet + +from printer import Printer + + +class SignedByteField(Field): + """Fields for a signed byte""" + def __init__(self, name, default): + Field.__init__(self, name, default, '