├── .gitignore ├── LICENSE ├── README.md ├── img ├── active-devices.png ├── new-and-missing-devices.png ├── signal-sift-main.png └── signal-sift-streamlit.png ├── requirements.txt └── signalsift.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Halcy0nic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SignalSift 2 | 3 | SignalSift is an application designed for autonomous analysis and comparison of Kismet wireless captures. 4 | 5 | ![signal-sift-main](./img/signal-sift-main.png) 6 | 7 | ## Description 8 | 9 | SignalSift is a powerful Streamlit-based tool that automates the analysis and comparison of Kismet wireless capture databases. It's designed for network administrators, security professionals, and technical surveillance countermeasures (TSCM) specialists who need to efficiently analyze changes in wireless environments. 10 | 11 | ## Features 12 | 13 | - Upload and compare baseline and follow-up Kismet captures 14 | - Generate comprehensive summaries of wireless activity 15 | - Identify top active devices in each capture 16 | - Detect new and missing devices between captures 17 | - Analyze device types, signal strengths, and packet counts 18 | - Intuitive web interface for easy visualization of results 19 | 20 | ## Installation 21 | 22 | 1. Clone the repository: 23 | ``` 24 | git clone https://github.com/Halcy0nic/SignalSift.git 25 | ``` 26 | 27 | 2. Navigate to the project directory: 28 | ``` 29 | cd signalsift 30 | ``` 31 | 32 | 3. Install the required dependencies: 33 | ``` 34 | pip install -r requirements.txt 35 | ``` 36 | 37 | ## Usage 38 | 39 | 1. Run the Streamlit app: 40 | ``` 41 | streamlit run signalsift.py 42 | ``` 43 | 44 | 2. Open your web browser and go to the URL provided by Streamlit (usually http://localhost:8501) 45 | 46 | ![signal-sift-streamlit](./img/signal-sift-streamlit.png) 47 | 48 | 3. Use the file upload buttons to select your baseline and follow-up Kismet database files 49 | 50 | 4. The application will automatically analyze and compare the captures, presenting the results in an easy-to-read format 51 | 52 | ![signal-sift-main](./img/signal-sift-main.png) 53 | 54 | ![active-devices](./img/active-devices.png) 55 | 56 | ![new-and-missing-devices](./img/new-and-missing-devices.png) 57 | 58 | ## Requirements 59 | 60 | - Python 3.7+ 61 | - Streamlit 62 | - SQLite3 63 | - Pandas 64 | 65 | See `requirements.txt` for a full list of dependencies. 66 | 67 | ## Common Issues 68 | 69 | ### File Uploading Issues 70 | 71 | If SignalSift is not properly loading your kismet file on Linux/MacOS, make sure your current user is the owner of the file. 72 | 73 | To change the ownership of files from root to your user account on Linux/MacOS, you can use the chown command with sudo privileges. Here's how to do it: 74 | 75 | Open a terminal on your system. Use the following command to change ownership of the Kismet db: 76 | 77 | ```bash 78 | sudo chown your_username:your_username filename 79 | ``` 80 | 81 | Replace your_username with your actual username and filename with the path to the file you want to change ownership of. For example, if your username is "john" and you want to change ownership of a file called "example.kismet" in your home directory, you would use: 82 | 83 | ```bash 84 | sudo chown john:john ~/example.kismet 85 | ``` 86 | 87 | For multiple files or directories, use: 88 | 89 | ```bash 90 | sudo chown -R your_username:your_username /path/to/directory 91 | ``` 92 | 93 | Verify the change with: 94 | 95 | ```bash 96 | ls -l filename 97 | ``` 98 | 99 | ### File Size Limitations 100 | 101 | #### Default File Size Limit 102 | 103 | By default, uploaded files are limited to a size of 200MB. This limit may affect users attempting to upload larger Kismet database files. 104 | 105 | #### Configuring Maximum Upload Size 106 | 107 | You can increase this limit by configuring the `server.maxUploadSize` option. Streamlit provides several ways to set this configuration: 108 | 109 | *Note: a value of 400 is 400 MB in size* 110 | 111 | 1. **Global Configuration File**: 112 | Create or edit a global config file at: 113 | - macOS/Linux: `~/.streamlit/config.toml` 114 | - Windows: `%userprofile%/.streamlit/config.toml` 115 | 116 | Add the following lines: 117 | ```toml 118 | [server] 119 | maxUploadSize = 400 120 | ``` 121 | 122 | 2. Project-specific Configuration: 123 | Create a config.toml file in a .streamlit folder in your SignalSift project directory: 124 | 125 | ``` 126 | SignalSift/ 127 | ├── .streamlit/ 128 | │ └── config.toml 129 | └── signalsift.py 130 | ``` 131 | 132 | In config.toml, add: 133 | 134 | ```toml 135 | [server] 136 | maxUploadSize = 400 137 | ``` 138 | 139 | 3. Command Line: 140 | When running your SignalSift, you can set the option via command line: 141 | 142 | ```bash 143 | streamlit run signalsift.py --server.maxUploadSize 400 144 | ``` 145 | 146 | ## Contributing 147 | 148 | Contributions to SignalSift are welcome! Please feel free to submit a Pull Request. 149 | 150 | ## License 151 | 152 | This project is licensed under the MIT License - see the LICENSE file for details. 153 | 154 | ## Acknowledgments 155 | 156 | Kismet Wireless for their excellent wireless network detector and sniffer -------------------------------------------------------------------------------- /img/active-devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Halcy0nic/SignalSift/070a200c9980aa2f474532bdb1226dbd12524c45/img/active-devices.png -------------------------------------------------------------------------------- /img/new-and-missing-devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Halcy0nic/SignalSift/070a200c9980aa2f474532bdb1226dbd12524c45/img/new-and-missing-devices.png -------------------------------------------------------------------------------- /img/signal-sift-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Halcy0nic/SignalSift/070a200c9980aa2f474532bdb1226dbd12524c45/img/signal-sift-main.png -------------------------------------------------------------------------------- /img/signal-sift-streamlit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Halcy0nic/SignalSift/070a200c9980aa2f474532bdb1226dbd12524c45/img/signal-sift-streamlit.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pandas 2 | streamlit -------------------------------------------------------------------------------- /signalsift.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import sqlite3 3 | import datetime 4 | from collections import defaultdict 5 | import pandas as pd 6 | import tempfile 7 | import os 8 | 9 | def connect_to_db(db_file): 10 | with tempfile.NamedTemporaryFile(delete=False) as tmp_file: 11 | tmp_file.write(db_file.getvalue()) 12 | tmp_file_path = tmp_file.name 13 | conn = sqlite3.connect(tmp_file_path) 14 | return conn, tmp_file_path 15 | 16 | def get_summary(conn): 17 | cursor = conn.cursor() 18 | 19 | cursor.execute("SELECT MIN(ts_sec), MAX(ts_sec) FROM packets") 20 | start_time, end_time = cursor.fetchone() 21 | 22 | cursor.execute("SELECT COUNT(*) FROM packets") 23 | packet_count = cursor.fetchone()[0] 24 | 25 | cursor.execute("SELECT COUNT(DISTINCT devmac) FROM devices") 26 | unique_devices = cursor.fetchone()[0] 27 | 28 | return { 29 | "Time Range": f"{datetime.datetime.fromtimestamp(start_time)} - {datetime.datetime.fromtimestamp(end_time)}", 30 | "Duration": f"{(end_time - start_time) / 3600:.2f} hours", 31 | "Unique Devices": unique_devices, 32 | "Packet Count": packet_count 33 | } 34 | 35 | 36 | def get_device_packet_counts(conn): 37 | cursor = conn.cursor() 38 | device_packet_counts = defaultdict(int) 39 | 40 | cursor.execute("SELECT sourcemac, COUNT(*) FROM packets GROUP BY sourcemac") 41 | for row in cursor.fetchall(): 42 | device_packet_counts[row[0]] += row[1] 43 | 44 | return device_packet_counts 45 | 46 | def get_devices(conn, packet_counts): 47 | cursor = conn.cursor() 48 | cursor.execute("SELECT devmac, phyname, type, strongest_signal FROM devices") 49 | devices_info = {} 50 | for row in cursor.fetchall(): 51 | devmac = row[0] 52 | devices_info[devmac] = { 53 | "phyname": row[1], 54 | "type": row[2], 55 | "signal": row[3], 56 | "packet_count": packet_counts.get(devmac, 0) 57 | } 58 | return devices_info 59 | 60 | def compare_captures(baseline, followup): 61 | new_devices = set(followup.keys()) - set(baseline.keys()) 62 | missing_devices = set(baseline.keys()) - set(followup.keys()) 63 | return new_devices, missing_devices 64 | 65 | def main(): 66 | st.title("Kismet Capture Comparison") 67 | 68 | baseline_file = st.file_uploader("Choose baseline Kismet database", type="kismet") 69 | followup_file = st.file_uploader("Choose follow-up Kismet database", type="kismet") 70 | 71 | if baseline_file and followup_file: 72 | baseline_conn, baseline_path = connect_to_db(baseline_file) 73 | followup_conn, followup_path = connect_to_db(followup_file) 74 | 75 | st.header("Baseline Summary") 76 | baseline_summary = get_summary(baseline_conn) 77 | st.table(pd.DataFrame.from_dict(baseline_summary, orient='index', columns=['Value'])) 78 | 79 | st.header("Follow-up Summary") 80 | followup_summary = get_summary(followup_conn) 81 | st.table(pd.DataFrame.from_dict(followup_summary, orient='index', columns=['Value'])) 82 | 83 | baseline_packet_counts = get_device_packet_counts(baseline_conn) 84 | followup_packet_counts = get_device_packet_counts(followup_conn) 85 | 86 | baseline_devices = get_devices(baseline_conn, baseline_packet_counts) 87 | followup_devices = get_devices(followup_conn, followup_packet_counts) 88 | 89 | st.header("Top 10 Most Active Devices in Baseline") 90 | baseline_top_devices = sorted(baseline_devices.items(), key=lambda x: x[1]['packet_count'], reverse=True)[:10] 91 | st.table(pd.DataFrame([(mac, info['phyname'], info['type'], info['signal'], info['packet_count']) 92 | for mac, info in baseline_top_devices], 93 | columns=["MAC", "PHY", "Type", "Signal", "Packet Count"])) 94 | 95 | st.header("Top 10 Most Active Devices in Follow-up") 96 | followup_top_devices = sorted(followup_devices.items(), key=lambda x: x[1]['packet_count'], reverse=True)[:10] 97 | st.table(pd.DataFrame([(mac, info['phyname'], info['type'], info['signal'], info['packet_count']) 98 | for mac, info in followup_top_devices], 99 | columns=["MAC", "PHY", "Type", "Signal", "Packet Count"])) 100 | 101 | new_devices, missing_devices = compare_captures(baseline_devices, followup_devices) 102 | 103 | st.header(f"New Devices Detected: {len(new_devices)}") 104 | if new_devices: 105 | st.table(pd.DataFrame([(mac, followup_devices[mac]["phyname"], followup_devices[mac]["type"], 106 | followup_devices[mac]["signal"], followup_devices[mac]["packet_count"]) 107 | for mac in new_devices], 108 | columns=["MAC", "PHY", "Type", "Signal", "Packet Count"])) 109 | 110 | st.header(f"Missing Devices (Present in Baseline, Not in Follow-up): {len(missing_devices)}") 111 | if missing_devices: 112 | st.table(pd.DataFrame([(mac, baseline_devices[mac]["phyname"], baseline_devices[mac]["type"], 113 | baseline_devices[mac]["signal"], baseline_devices[mac]["packet_count"]) 114 | for mac in missing_devices], 115 | columns=["MAC", "PHY", "Type", "Signal", "Packet Count"])) 116 | 117 | baseline_conn.close() 118 | followup_conn.close() 119 | os.unlink(baseline_path) 120 | os.unlink(followup_path) 121 | 122 | if __name__ == "__main__": 123 | main() 124 | --------------------------------------------------------------------------------