before big cleaning of new method
This commit is contained in:
parent
34d3fbb350
commit
1d485bc890
3 changed files with 293 additions and 24 deletions
|
@ -117,6 +117,8 @@ sudo $(which poetry) run python3 ctiger.py -h
|
|||
* Website: http://anoduck.github.io
|
||||
* Github: [@anoduck](https://github.com/anoduck)
|
||||
|
||||
[![built with Codeium](https://codeium.com/badges/main)](https://codeium.com)
|
||||
|
||||
## Show your support
|
||||
|
||||
Give a ⭐️ if this project helped you!
|
||||
|
|
198
ctiger.py
198
ctiger.py
|
@ -9,7 +9,6 @@ Created on Fri Jul 21 17:05:07 2023
|
|||
|
||||
import os
|
||||
import sys
|
||||
import fcntl
|
||||
import argparse
|
||||
from scapy.sendrecv import sniff
|
||||
from scapy.sendrecv import AsyncSniffer
|
||||
|
@ -24,7 +23,6 @@ from scapy.layers.dot11 import Dot11FCS
|
|||
from scapy.config import Conf as scapyconfig
|
||||
from scapy.layers.eap import EAPOL
|
||||
from scapy.utils import PcapWriter
|
||||
from getmac import get_mac_address
|
||||
# Import Faker.
|
||||
from faker import Faker
|
||||
# Import the WifiESSID class from Faker Wi-Fi ESSID.
|
||||
|
@ -33,15 +31,14 @@ from faker import Faker
|
|||
# from scapy_ex import Dot11Elt
|
||||
from art.art import tprint
|
||||
from dataclasses import dataclass
|
||||
import struct
|
||||
import multiprocessing as mp
|
||||
import asyncio
|
||||
import threading
|
||||
from random import choice, randint
|
||||
from threading import Thread
|
||||
from random import choice
|
||||
from configobj import ConfigObj, validate
|
||||
from collections import Counter
|
||||
import pandas as pd
|
||||
import socket
|
||||
import signal
|
||||
import logging
|
||||
from time import sleep
|
||||
|
@ -214,24 +211,36 @@ def PRN2(pkt):
|
|||
# print("Sequence Control:", frame.SC)
|
||||
# print(feature(frame))
|
||||
# print("\n")
|
||||
# ------------------------------------------------------------------
|
||||
# Five choices for Duration/ID:
|
||||
# 1. Calculated per packet
|
||||
# 2. Or one of the following: 16383, 26370, 32767, 65535
|
||||
#
|
||||
# Calculated as:
|
||||
# bytes = struct.pack("<H",microsec)
|
||||
# timeval = struct.unpack(">H", bytes)[0]
|
||||
# ------------------------------------------------------------------
|
||||
def strainer(pkt) -> None:
|
||||
if pkt[Dot11].type == 0 and pkt[Dot11].subtype == 4:
|
||||
bssid = pkt[Dot11FCS].addr2
|
||||
log.info('BSSID for strainer: {0}'.format(bssid))
|
||||
log.debug('Local Macaddr is: {0}'.format(macaddr))
|
||||
idval = [16383, 26370, 32767, 65535]
|
||||
durid = choice(idval)
|
||||
new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11,
|
||||
addr1=bssid,
|
||||
addr2=macaddr,
|
||||
ID=65535)
|
||||
ID=durid)
|
||||
log.debug('Sending RTS frame to {0} with type 11'.format(bssid))
|
||||
res = srp1(new_pkt, timeout=3, verbose=0, retry=0)
|
||||
if res:
|
||||
res = srp1(new_pkt, timeout=3, verbose=0, retry=0, threaded=True)
|
||||
if res is not None:
|
||||
if res[Dot11].type == 1 and res[Dot11].subtype == 12:
|
||||
log.debug('Recieved CTS packet.')
|
||||
log.info('Intercepted CTS from: {0}'.format(bssid))
|
||||
dbm_signal = pkt.dBm_AntSignal
|
||||
channel = extract_channel(res[Dot11])
|
||||
scan_df.loc[bssid] = ['N/A', dbm_signal, channel, 'N/A']
|
||||
scan_df.to_csv(valid_file, mode='a')
|
||||
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
|
@ -312,7 +321,8 @@ class NetDev:
|
|||
mon_crtd = self.interface + 'mon'
|
||||
self.mon_crtd = mon_crtd
|
||||
if self.mon_type == 'create':
|
||||
self.create_if()
|
||||
self.create_if(interface=self.interface,
|
||||
macaddr=self.macaddr, mon_crtd=mon_crtd)
|
||||
mon_if = self.mon_crtd
|
||||
return mon_if
|
||||
elif self.mon_type == 'switch':
|
||||
|
@ -325,15 +335,6 @@ class NetDev:
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def signal_handler(signal, frame) -> None:
|
||||
print('You pressed Ctrl+C!')
|
||||
log.info('Shutting down')
|
||||
scan_df.to_csv('ct_purge.csv')
|
||||
log.info('Saved results to: {0}'.format('ct_purge.csv'))
|
||||
log.info('Going Down!!')
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗
|
||||
# ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝
|
||||
# ██████╔╝██║ ██║██████╔╝██║ ███╗█████╗
|
||||
|
@ -349,6 +350,9 @@ class Purge(object):
|
|||
self.valid_file = kwargs.get('valid_file')
|
||||
self.channels = kwargs.get('channels')
|
||||
|
||||
def __getitem__(self, pkt):
|
||||
return pkt
|
||||
|
||||
def channel_runner(self, mon_if, channels) -> None:
|
||||
self.mon_if = mon_if
|
||||
self.channels = channels
|
||||
|
@ -385,6 +389,7 @@ class Purge(object):
|
|||
# log.info('We will be writing captured macs to ', str(self.valid_file))
|
||||
chop = asyncio.to_thread(self.channel_runner,
|
||||
self.mon_if, self.channels)
|
||||
global chopper
|
||||
chopper = asyncio.create_task(chop)
|
||||
log.info('Channel runner started.')
|
||||
await asyncio.sleep(0)
|
||||
|
@ -398,12 +403,151 @@ class Purge(object):
|
|||
forever_wait = threading.Event()
|
||||
forever_wait.wait()
|
||||
|
||||
def send_pkt(self, bssid) -> None:
|
||||
self.bssid = bssid
|
||||
idval = [16383, 26370, 32767, 65535]
|
||||
durid = choice(idval)
|
||||
new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11,
|
||||
addr1=bssid,
|
||||
addr2=macaddr,
|
||||
ID=durid)
|
||||
log.debug('Sending RTS frame to {0} with type 11'.format(bssid))
|
||||
sendp(new_pkt, timeout=3, verbose=0, retry=0, threaded=True)
|
||||
|
||||
async def get_interface(self, interface, mon_type) -> str:
|
||||
self.interface = interface
|
||||
self.mon_type = mon_type
|
||||
ndev = NetDev(interface=self.interface, mon_type=self.mon_type)
|
||||
mon_if = ndev.start_monitor(interface=self.interface,
|
||||
mon_type=self.mon_type)
|
||||
return mon_if
|
||||
|
||||
async def random_chan(self, channels):
|
||||
self.channels = channels
|
||||
chlist = self.channels.split(',')
|
||||
chans = [int(chan) for chan in chlist]
|
||||
ichan = choice(chans)
|
||||
return ichan
|
||||
|
||||
async def change_chan(self, mon_if, ichan):
|
||||
self.mon_if = mon_if
|
||||
self.ichan = ichan
|
||||
log.debug('Monitor interface: {0}'.format(self.mon_if))
|
||||
log.debug('New Channel: {0}'.format(self.ichan))
|
||||
# iw [options] dev <devname> set channel <channel>
|
||||
change_it = os.system(
|
||||
'iw dev ' + self.mon_if + ' set channel ' + self.ichan
|
||||
)
|
||||
if change_it:
|
||||
current_channel = self.ichan
|
||||
return current_channel
|
||||
|
||||
def probe_proc(self, probe_pkts):
|
||||
ptup_list = []
|
||||
for ppkt in probe_pkts:
|
||||
dbm_signal = ppkt.dBm_AntSignal
|
||||
self.dbm_signal = dbm_signal
|
||||
bssid = ppkt[Dot11FCS].addr2
|
||||
self.bssid = bssid
|
||||
ptuple = (bssid, dbm_signal)
|
||||
ptup_list.append(ptuple)
|
||||
punified = list(set(ptup_list))
|
||||
return punified
|
||||
|
||||
def cts_prn(self, pkt):
|
||||
dbm_signal = pkt.dBm_AntSignal
|
||||
ichan = extract_channel(pkt[Dot11])
|
||||
scan_df.loc[self.bssid] = [macaddr, dbm_signal, ichan, 'N/A']
|
||||
scan_df.to_csv(valid_file, mode='a')
|
||||
log.info('Results written to {0}'.format(valid_file))
|
||||
|
||||
async def chan_timer(self):
|
||||
await asyncio.sleep(14.7)
|
||||
log.info('Channel timer done.')
|
||||
|
||||
def probe_prn(self, pkt):
|
||||
bssid = pkt[Dot11FCS].addr2
|
||||
self.bssid = bssid
|
||||
log.info('Sending RTS frame to {0}'.format(bssid))
|
||||
self.send_pkt(bssid)
|
||||
return
|
||||
|
||||
async def mac_revealer(self, interface, mon_type, valid_file, channels):
|
||||
log.info('mac revealer started')
|
||||
self.interface = interface
|
||||
self.mon_type = mon_type
|
||||
self.valid_file = valid_file
|
||||
self.channels = channels
|
||||
log.info('setting up class attributes')
|
||||
global scan_df
|
||||
scan_df = get_df()
|
||||
log.info('acquired Dataframe')
|
||||
mon_if = await self.get_interface(self.interface, self.mon_type)
|
||||
log.debug('mon_if: {0}'.format(mon_if))
|
||||
log.debug('return type: {0}'.format(type(mon_if)))
|
||||
self.mon_if = mon_if
|
||||
log.info('interface {0} is up and running.'.format(self.mon_if))
|
||||
chop = asyncio.to_thread(self.channel_runner,
|
||||
self.mon_if, self.channels)
|
||||
global chopper
|
||||
chopper = asyncio.create_task(chop)
|
||||
log.info('Channel runner started.')
|
||||
while True:
|
||||
probe_sniff = AsyncSniffer(
|
||||
iface=mon_if, prn=self.probe_prn,
|
||||
filter="type mgt subtype probe-req",
|
||||
monitor=True)
|
||||
probe_sniff.start()
|
||||
log.info('Probe sniffer started')
|
||||
# presult = await self.probe_proc(probe_sniff.results)
|
||||
# log.info('Processing results from probe sniffer.')
|
||||
# bssid = presult[0]
|
||||
# dbm_signal = presult[1]
|
||||
await asyncio.sleep(0)
|
||||
# await self.send_pkt(bssid)
|
||||
# log.info('Sending RTS frame to {0}'.format(bssid))
|
||||
# await asyncio.sleep(0)
|
||||
cts_sniff = AsyncSniffer(filter='type ctl subtype cts',
|
||||
iface=mon_if, prn=self.cts_prn,
|
||||
monitor=True)
|
||||
cts_sniff.start()
|
||||
log.info('CTS sniffer started')
|
||||
await asyncio.sleep(0)
|
||||
# valid_cts = await self.cts_proc(cts_sniff.results)
|
||||
# log.info('Validating CTS packet.')
|
||||
# if valid_cts:
|
||||
# scan_df.loc[bssid] = [macaddr, dbm_signal, ichan, 'N/A']
|
||||
# scan_df.to_csv(valid_file, mode='a')
|
||||
# log.info('Results written to {0}'.format(valid_file))
|
||||
# else:
|
||||
# log.info('Unable to validate CTS packet.')
|
||||
# await asyncio.sleep(0)
|
||||
# alltasks = asyncio.all_tasks()
|
||||
# current_task = asyncio.current_task()
|
||||
# alltasks.remove(asyncio.current_task)
|
||||
# await asyncio.wait(alltasks)
|
||||
# ttimer = asyncio.create_task(self.chan_timer())
|
||||
# await ttimer
|
||||
# log.info('Beginning channel hopping.')
|
||||
# ichan = await self.random_chan(self.channels)
|
||||
# self.ichan = ichan
|
||||
# await asyncio.create_task(self.change_chan(self.mon_if, self.ichan))
|
||||
# await asyncio.sleep(0)
|
||||
# continue
|
||||
|
||||
def start_purge(self) -> None:
|
||||
asyncio.run(self.mac_purge(self.interface,
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
print('Enter Ctrl+C TWICE to fully stop the script.')
|
||||
# asyncio.run(self.mac_purge(self.interface,
|
||||
# self.mon_type,
|
||||
# self.valid_file,
|
||||
# self.channels))
|
||||
asyncio.run(self.mac_revealer(self.interface,
|
||||
self.mon_type,
|
||||
self.valid_file,
|
||||
self.channels))
|
||||
forever_wait = threading.Event()
|
||||
forever_wait.wait()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
@ -613,11 +757,21 @@ def get_df():
|
|||
return scan_df
|
||||
|
||||
|
||||
def get_log(log_file):
|
||||
def signal_handler(signal, frame) -> None:
|
||||
print('You pressed Ctrl+C!')
|
||||
log.info('Shutting down')
|
||||
log.info('Going Down!!')
|
||||
exit(0)
|
||||
|
||||
|
||||
def get_log(log_file, log_level):
|
||||
logfile = os.path.abspath(log_file)
|
||||
log = logging.getLogger('scapy.runtime')
|
||||
if log.hasHandlers():
|
||||
log.handlers.clear()
|
||||
# lvl = str(f'logging.{log_level}')
|
||||
# lgvl = lvl.strip()
|
||||
# log.setLevel(lgvl)
|
||||
log.setLevel(logging.DEBUG)
|
||||
handler = logging.FileHandler(logfile, mode='a', encoding='utf-8')
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
@ -648,7 +802,7 @@ def process_args(args: argparse.Namespace) -> None:
|
|||
None
|
||||
"""
|
||||
global log
|
||||
log = get_log(args.log_file)
|
||||
log = get_log(args.log_file, args.log_level)
|
||||
log.info('Started crouching tiger')
|
||||
log.info('Started logger...')
|
||||
match args.module:
|
||||
|
@ -660,6 +814,8 @@ def process_args(args: argparse.Namespace) -> None:
|
|||
#mon_dev, mon_type, valid_file, channels
|
||||
#mon_dev, mon_type, valid_file, channels
|
||||
log.debug('args: {0}'.format(args))
|
||||
global valid_file
|
||||
valid_file = args.valid_file
|
||||
pge = Purge(interface=args.name,
|
||||
mon_type=args.mon_type,
|
||||
valid_file=args.valid_file,
|
||||
|
|
|
@ -1963,6 +1963,117 @@
|
|||
"DEBUG: Invalid monitor type\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Custom Scapy Sessions\n",
|
||||
"\n",
|
||||
"One can configure their own scapy session, "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# SPDX-License-Identifier: GPL-2.0-only\n",
|
||||
"# This file is part of Scapy\n",
|
||||
"# See https://scapy.net/ for more information\n",
|
||||
"\n",
|
||||
"\"\"\"\n",
|
||||
"Sessions: decode flow of packets when sniffing\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"from collections import defaultdict\n",
|
||||
"import socket\n",
|
||||
"import struct\n",
|
||||
"\n",
|
||||
"from scapy.compat import orb\n",
|
||||
"from scapy.config import conf\n",
|
||||
"from scapy.packet import NoPayload, Packet\n",
|
||||
"from scapy.pton_ntop import inet_pton\n",
|
||||
"\n",
|
||||
"# Typing imports\n",
|
||||
"from typing import (\n",
|
||||
" Any,\n",
|
||||
" DefaultDict,\n",
|
||||
" Dict,\n",
|
||||
" Iterator,\n",
|
||||
" List,\n",
|
||||
" Optional,\n",
|
||||
" Tuple,\n",
|
||||
" cast,\n",
|
||||
" TYPE_CHECKING,\n",
|
||||
")\n",
|
||||
"from scapy.compat import Self\n",
|
||||
"if TYPE_CHECKING:\n",
|
||||
" from scapy.supersocket import SuperSocket\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# You will only need to extend the Default Session\n",
|
||||
"class DefaultSession(object):\n",
|
||||
" \"\"\"Default session: no stream decoding\"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, supersession: Optional[Self] = None):\n",
|
||||
" if supersession and not isinstance(supersession, DefaultSession):\n",
|
||||
" supersession = supersession()\n",
|
||||
" self.supersession = supersession\n",
|
||||
"\n",
|
||||
" def process(self, pkt: Packet) -> Optional[Packet]:\n",
|
||||
" \"\"\"\n",
|
||||
" Called to pre-process the packet\n",
|
||||
" \"\"\"\n",
|
||||
" # Optionally handle supersession\n",
|
||||
" if self.supersession:\n",
|
||||
" return self.supersession.process(pkt)\n",
|
||||
" return pkt\n",
|
||||
"\n",
|
||||
" def recv(self, sock: 'SuperSocket') -> Iterator[Packet]:\n",
|
||||
" \"\"\"\n",
|
||||
" Will be called by sniff() to ask for a packet\n",
|
||||
" \"\"\"\n",
|
||||
" pkt = sock.recv()\n",
|
||||
" if not pkt:\n",
|
||||
" return\n",
|
||||
" pkt = self.process(pkt)\n",
|
||||
" if pkt:\n",
|
||||
" yield pkt\n",
|
||||
" \n",
|
||||
" \n",
|
||||
"class Dot11Session(DefaultSession):\n",
|
||||
" \"\"\"Decode Dot11 packets 'on-the-flow'.\n",
|
||||
"\n",
|
||||
" Usage:\n",
|
||||
" >>> sniff(session=Dot11Session)\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, *args, **kwargs):\n",
|
||||
" # type: (*Any, **Any) -> None\n",
|
||||
" DefaultSession.__init__(self, *args, **kwargs)\n",
|
||||
" self.keys = {} # type: Dict[Tuple[Any, ...], List[Packet]] # noqa: E501\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class IPSession(DefaultSession):\n",
|
||||
" \"\"\"Defragment IP packets 'on-the-flow'.\n",
|
||||
"\n",
|
||||
" Usage:\n",
|
||||
" >>> sniff(session=IPSession)\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, *args, **kwargs):\n",
|
||||
" # type: (*Any, **Any) -> None\n",
|
||||
" DefaultSession.__init__(self, *args, **kwargs)\n",
|
||||
" self.fragments = defaultdict(list) # type: DefaultDict[Tuple[Any, ...], List[Packet]] # noqa: E501\n",
|
||||
"\n",
|
||||
" def process(self, packet: Packet) -> Optional[Packet]:\n",
|
||||
" from scapy.layers.inet import IP, _defrag_ip_pkt\n",
|
||||
" if IP not in packet:\n",
|
||||
" return packet\n",
|
||||
" return _defrag_ip_pkt(packet, self.fragments)[1] # type: ignore\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
|
|
Loading…
Reference in a new issue