├── Capture1.PNG ├── requirements.txt ├── README.md ├── .gitignore ├── stegsleuth_gui.py └── stegsleuth.py /Capture1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/40sp3l/stegsleuth/HEAD/Capture1.PNG -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pillow 2 | numpy 3 | moviepy 4 | colorama 5 | art 6 | tk 7 | pytesseract 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StegSleuth 2 | Steganography Detection Framework Used For Analyzing Files i.e PDFs, Images, Videos, & Audios To Uncover Hidden Data, Gems Or Secrets. 3 | 4 | # Install Requirements 5 | pip install -r requirements.txt 6 | 7 | sudo apt-get install tesseract-ocr -y 8 | 9 | sudo apt-get install exiftool -y 10 | 11 | sudo apt-get install pdfinfo -y 12 | 13 | # Export PATH 14 | export PATH="/usr/bin:$PATH" 15 | 16 | # Start StegSleuth 17 | python3 stegsleuth.py 18 | 19 | # Start StegSleuth GUI 20 | python3 stegsleuth_gui.py 21 | -------------------------------------------------------------------------------- /.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 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | #uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | #poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # PyPI configuration file 171 | .pypirc 172 | -------------------------------------------------------------------------------- /stegsleuth_gui.py: -------------------------------------------------------------------------------- 1 | # stegsleuth - a steganography detection framework 2 | # author - @cyb3rf034r3ss 3 | import os 4 | import shutil 5 | import tkinter as tk 6 | from tkinter import filedialog, messagebox 7 | from PIL import Image 8 | import numpy as np 9 | import wave 10 | from moviepy import VideoFileClip 11 | 12 | 13 | class AssetManager: 14 | def __init__(self, asset_folder='assets'): 15 | self.asset_folder = asset_folder 16 | self.assets = [] 17 | 18 | def list_assets(self): 19 | self.assets = [f for f in os.listdir(self.asset_folder) if os.path.isfile(os.path.join(self.asset_folder, f))] 20 | return self.assets 21 | 22 | def upload_asset(self, source_path): 23 | if os.path.isfile(source_path): 24 | destination_path = os.path.join(self.asset_folder, os.path.basename(source_path)) 25 | shutil.copy(source_path, destination_path) 26 | return True 27 | else: 28 | return False 29 | 30 | def select_asset(self, asset_number): 31 | if 0 < asset_number <= len(self.assets): 32 | return self.assets[asset_number - 1] 33 | else: 34 | return None 35 | 36 | 37 | class SteganographyDetect: 38 | @staticmethod 39 | def detect_lsb_steganography(image_path): 40 | image = Image.open(image_path) 41 | image_array = np.array(image) 42 | hidden_data = [] 43 | 44 | for row in image_array: 45 | for pixel in row: 46 | hidden_data.append(pixel[0] & 1) 47 | hidden_data.append(pixel[1] & 1) 48 | hidden_data.append(pixel[2] & 1) 49 | 50 | hidden_message = ''.join(map(str, hidden_data)) 51 | return hidden_message 52 | 53 | @staticmethod 54 | def detect_audio_steganography(audio_path): 55 | audio = wave.open(audio_path, 'rb') 56 | frames = audio.readframes(audio.getnframes()) 57 | audio_data = np.frombuffer(frames, dtype=np.int16) 58 | 59 | anomalies = np.where(audio_data % 2 != 0)[0] 60 | return anomalies 61 | 62 | @staticmethod 63 | def detect_video_steganography(video_path): 64 | video = VideoFileClip(video_path) 65 | frame_data = [] 66 | 67 | for frame in video.iter_frames(fps=1, dtype="uint8"): 68 | frame_data.append(frame.sum()) 69 | 70 | return frame_data 71 | 72 | 73 | class StegSleuthGUI: 74 | def __init__(self, root): 75 | self.root = root 76 | self.root.title("StegSleuth - Steganography Detection") 77 | self.root.geometry("600x400") 78 | self.asset_manager = AssetManager() 79 | self.selected_asset = None 80 | self.selected_asset_type = None 81 | 82 | self.create_widgets() 83 | 84 | def create_widgets(self): 85 | 86 | self.list_button = tk.Button(self.root, text="List Assets", command=self.list_assets) 87 | self.list_button.pack(pady=10) 88 | 89 | 90 | self.upload_button = tk.Button(self.root, text="Upload Asset", command=self.upload_asset) 91 | self.upload_button.pack(pady=10) 92 | 93 | 94 | self.asset_label = tk.Label(self.root, text="Select Asset") 95 | self.asset_label.pack() 96 | 97 | self.asset_dropdown = tk.StringVar() 98 | self.asset_dropdown.set("Select Asset") 99 | self.asset_menu = tk.OptionMenu(self.root, self.asset_dropdown, []) 100 | self.asset_menu.pack(pady=10) 101 | 102 | 103 | self.analyze_button = tk.Button(self.root, text="Analyze Selected Asset", command=self.analyze_asset) 104 | self.analyze_button.pack(pady=10) 105 | 106 | 107 | self.result_text = tk.Text(self.root, height=10, width=60) 108 | self.result_text.pack(pady=10) 109 | 110 | def list_assets(self): 111 | assets = self.asset_manager.list_assets() 112 | if not assets: 113 | self.show_message("No assets found in the 'assets' folder.") 114 | else: 115 | self.asset_dropdown.set("Select Asset") 116 | self.asset_menu['menu'].delete(0, 'end') 117 | for asset in assets: 118 | self.asset_menu['menu'].add_command(label=asset, command=tk._setit(self.asset_dropdown, asset)) 119 | 120 | def upload_asset(self): 121 | file_path = filedialog.askopenfilename(title="Select a File to Upload") 122 | if file_path: 123 | if self.asset_manager.upload_asset(file_path): 124 | self.show_message(f"File uploaded successfully: {file_path}") 125 | else: 126 | self.show_message(f"Failed to upload: {file_path}") 127 | 128 | def analyze_asset(self): 129 | asset_name = self.asset_dropdown.get() 130 | if asset_name == "Select Asset": 131 | self.show_message("Please select an asset first.") 132 | return 133 | 134 | asset_path = os.path.join(self.asset_manager.asset_folder, asset_name) 135 | 136 | 137 | self.selected_asset_type = self.detect_asset_type(asset_name) 138 | 139 | if self.selected_asset_type == "image": 140 | self.analyze_image(asset_path) 141 | elif self.selected_asset_type == "audio": 142 | self.analyze_audio(asset_path) 143 | elif self.selected_asset_type == "video": 144 | self.analyze_video(asset_path) 145 | else: 146 | self.show_message("Unsupported file type.") 147 | 148 | def analyze_image(self, image_file): 149 | self.result_text.delete(1.0, tk.END) 150 | hidden_message = SteganographyDetect.detect_lsb_steganography(image_file) 151 | if hidden_message: 152 | self.result_text.insert(tk.END, f"Hidden data detected in image: {hidden_message[:100]}...\n") 153 | else: 154 | self.result_text.insert(tk.END, "No hidden data detected in the image.\n") 155 | 156 | def analyze_audio(self, audio_file): 157 | self.result_text.delete(1.0, tk.END) 158 | anomalies = SteganographyDetect.detect_audio_steganography(audio_file) 159 | if anomalies.size > 0: 160 | self.result_text.insert(tk.END, f"Frequency anomalies detected in audio: {anomalies[:10]}...\n") 161 | else: 162 | self.result_text.insert(tk.END, "No hidden data detected in the audio.\n") 163 | 164 | def analyze_video(self, video_file): 165 | self.result_text.delete(1.0, tk.END) 166 | frame_data = SteganographyDetect.detect_video_steganography(video_file) 167 | if frame_data: 168 | self.result_text.insert(tk.END, f"Possible hidden data detected in video frames: {frame_data[:10]}...\n") 169 | else: 170 | self.result_text.insert(tk.END, "No hidden data detected in the video.\n") 171 | 172 | def detect_asset_type(self, asset_name): 173 | if asset_name.lower().endswith(('.png', '.jpg', '.jpeg')): 174 | return 'image' 175 | elif asset_name.lower().endswith(('.wav', '.mp3')): 176 | return 'audio' 177 | elif asset_name.lower().endswith(('.mp4', '.avi')): 178 | return 'video' 179 | else: 180 | return 'unknown' 181 | 182 | def show_message(self, message): 183 | messagebox.showinfo("StegSleuth", message) 184 | 185 | 186 | 187 | if __name__ == "__main__": 188 | root = tk.Tk() 189 | gui = StegSleuthGUI(root) 190 | root.mainloop() 191 | -------------------------------------------------------------------------------- /stegsleuth.py: -------------------------------------------------------------------------------- 1 | # stegsleuth - a steganography detection framework 2 | # author - @cyb3rf034r3ss 3 | import os 4 | import readline 5 | import sys 6 | import shutil 7 | from PIL import Image 8 | import pytesseract 9 | import numpy as np 10 | import wave 11 | import subprocess 12 | from moviepy import VideoFileClip 13 | from colorama import init, Fore, Back, Style 14 | import art 15 | 16 | if os.name == 'posix': 17 | os.system('clear') 18 | else: 19 | exit() 20 | 21 | init(autoreset=True) 22 | 23 | print(art.text2art("StegSleuth")) 24 | 25 | class AssetManager: 26 | def __init__(self, asset_folder='assets'): 27 | self.asset_folder = asset_folder 28 | self.assets = [] 29 | 30 | def list_assets(self): 31 | self.assets = [f for f in os.listdir(self.asset_folder) if os.path.isfile(os.path.join(self.asset_folder, f))] 32 | return self.assets 33 | 34 | def select_asset(self, asset_number): 35 | if 0 < asset_number <= len(self.assets): 36 | return self.assets[asset_number - 1] 37 | else: 38 | print(Fore.RED + "\nInvalid selection.\n") 39 | return None 40 | 41 | def upload_asset(self, source_path): 42 | # Copy the file to the assets folder 43 | if os.path.isfile(source_path): 44 | destination_path = os.path.join(self.asset_folder, os.path.basename(source_path)) 45 | shutil.copy(source_path, destination_path) 46 | print(Fore.GREEN + f"\n[*] File '{source_path}' uploaded successfully to '{self.asset_folder}'.\n") 47 | else: 48 | print(Fore.RED + f"\nInvalid file path: {source_path}. Upload failed.\n") 49 | 50 | def detect_lsb_steganography(image_path): 51 | image = Image.open(image_path) 52 | image_array = np.array(image) 53 | hidden_data = [] 54 | 55 | for row in image_array: 56 | for pixel in row: 57 | hidden_data.append(pixel[0] & 1) 58 | hidden_data.append(pixel[1] & 1) 59 | hidden_data.append(pixel[2] & 1) 60 | 61 | hidden_message = ''.join(map(str, hidden_data)) 62 | return hidden_message 63 | 64 | def detect_audio_steganography(audio_path): 65 | audio = wave.open(audio_path, 'rb') 66 | frames = audio.readframes(audio.getnframes()) 67 | audio_data = np.frombuffer(frames, dtype=np.int16) 68 | 69 | anomalies = np.where(audio_data % 2 != 0)[0] 70 | return anomalies 71 | 72 | def detect_video_steganography(video_path): 73 | video = VideoFileClip(video_path) 74 | frame_data = [] 75 | 76 | for frame in video.iter_frames(fps=1, dtype="uint8"): 77 | frame_data.append(frame.sum()) 78 | 79 | return frame_data 80 | 81 | class SteganographyConsole: 82 | def __init__(self): 83 | self.asset_manager = AssetManager() 84 | self.selected_asset = None 85 | self.selected_asset_type = None 86 | 87 | def run(self): 88 | print(Fore.GREEN + "[Author] @cyb3rf034r3ss") 89 | print(Fore.GREEN + "[StegSleuth] Steganography Detection Framework!") 90 | print() 91 | while True: 92 | command = input(Fore.YELLOW + "[StegSleuth] > ").strip() 93 | 94 | if command == 'list assets': 95 | self.list_assets() 96 | elif command.startswith('select'): 97 | self.select_asset(command) 98 | elif command == 'analyze': 99 | self.analyze_selected_file() 100 | elif command == 'help': 101 | self.display_help() 102 | elif command.startswith('upload'): 103 | self.upload_asset(command) 104 | elif command == 'exit': 105 | print(Fore.RED + "\nExiting framework...\n") 106 | sys.exit() 107 | else: 108 | print(Fore.RED + "\nUnknown command. Try again.\n") 109 | 110 | def list_assets(self): 111 | assets = self.asset_manager.list_assets() 112 | if not assets: 113 | print(Fore.RED + "\nNo assets found.\n") 114 | else: 115 | print(Fore.CYAN + "\n[*] Assets in 'assets' folder:\n") 116 | for idx, asset in enumerate(assets, start=1): 117 | print(Fore.CYAN + f" {idx}. {asset}\n") 118 | 119 | def select_asset(self, command): 120 | try: 121 | asset_number = int(command.split()[1]) 122 | selected_asset = self.asset_manager.select_asset(asset_number) 123 | if selected_asset: 124 | self.selected_asset = selected_asset 125 | self.selected_asset_type = self.detect_asset_type(selected_asset) 126 | print(Fore.GREEN + f"\n[*] Selected asset: {selected_asset}\n") 127 | print(Fore.YELLOW + f"[*] File type detected: {self.selected_asset_type}\n") 128 | except (IndexError, ValueError): 129 | print(Fore.RED + "\nUsage: select \n") 130 | 131 | def detect_asset_type(self, asset): 132 | if asset.lower().endswith(('.png', '.jpg', '.jpeg')): 133 | return 'image' 134 | elif asset.lower().endswith(('.wav', '.mp3')): 135 | return 'audio' 136 | elif asset.lower().endswith(('.mp4', '.avi')): 137 | return 'video' 138 | elif asset.lower().endswith('.pdf'): 139 | return 'pdf' 140 | else: 141 | return 'unknown' 142 | 143 | def analyze_selected_file(self): 144 | if not self.selected_asset: 145 | print(Fore.RED + "\nNo asset selected. Please select a file first.\n") 146 | return 147 | 148 | asset_path = os.path.join(self.asset_manager.asset_folder, self.selected_asset) 149 | 150 | if self.selected_asset_type == 'image': 151 | self.analyze_image(asset_path) 152 | elif self.selected_asset_type == 'audio': 153 | self.analyze_audio(asset_path) 154 | elif self.selected_asset_type == 'video': 155 | self.analyze_video(asset_path) 156 | elif self.selected_asset_type == 'pdf': 157 | self.analyze_pdf(asset_path) 158 | else: 159 | print(Fore.RED + "\nUnsupported file type.\n") 160 | 161 | def analyze_image(self, image_file): 162 | print(Fore.GREEN + f"\n[*] Running image analysis on {image_file}...\n") 163 | hidden_message = detect_lsb_steganography(image_file) 164 | if hidden_message: 165 | print(Fore.YELLOW + f"Hidden data detected in image: {hidden_message[:100]}...\n") 166 | else: 167 | print(Fore.RED + "\nNo hidden data detected in the image.\n") 168 | print(Fore.GREEN + f"[*] Exif Data\n") 169 | exif_data = subprocess.check_output(f'exiftool {image_file}', shell=True) 170 | print(exif_data.decode()) 171 | image = Image.open(image_file) 172 | 173 | text = pytesseract.image_to_string(image) 174 | 175 | print(Fore.GREEN + f"[*] Extracted Text From Image\n") 176 | print(text) 177 | 178 | def analyze_audio(self, audio_file): 179 | print(Fore.GREEN + f"[*] Running audio analysis on {audio_file}...") 180 | anomalies = detect_audio_steganography(audio_file) 181 | if anomalies.size > 0: 182 | print(Fore.YELLOW + f"Frequency anomalies detected in audio: {anomalies[:10]}...") 183 | else: 184 | print(Fore.RED + "No hidden data detected in the audio.") 185 | 186 | def analyze_video(self, video_file): 187 | print(Fore.GREEN + f"[*] Running video analysis on {video_file}...") 188 | frame_data = detect_video_steganography(video_file) 189 | if frame_data: 190 | print(Fore.YELLOW + f"Possible hidden data detected in video frames: {frame_data[:10]}...") 191 | else: 192 | print(Fore.RED + "No hidden data detected in the video.") 193 | 194 | def analyze_pdf(self, pdf_file): 195 | print(Fore.GREEN + f"\n[*] Running PDF analysis on {pdf_file}...\n") 196 | try: 197 | 198 | pdf_info = subprocess.check_output(f'pdfinfo {pdf_file}', shell=True) 199 | print(Fore.YELLOW + f"PDF Metadata:\n{pdf_info.decode()}") 200 | except subprocess.CalledProcessError as e: 201 | print(Fore.RED + f"Error extracting PDF metadata: {e}\n") 202 | 203 | def display_help(self): 204 | print(Fore.CYAN + "\nHelp Menu:") 205 | print(Fore.GREEN + "list assets - Lists all assets in the 'assets' folder") 206 | print(Fore.GREEN + "select - Selects an asset by its number") 207 | print(Fore.GREEN + "analyze - Analyzes the selected asset for hidden data") 208 | print(Fore.GREEN + "help - Displays this help menu") 209 | print(Fore.GREEN + "upload - Uploads a file to the 'assets' folder") 210 | print(Fore.GREEN + "exit - Exits the framework") 211 | print() 212 | 213 | def upload_asset(self, command): 214 | try: 215 | file_path = command.split()[1] 216 | self.asset_manager.upload_asset(file_path) 217 | except IndexError: 218 | print(Fore.RED + "\nUsage: upload \n") 219 | 220 | 221 | def main(): 222 | console = SteganographyConsole() 223 | console.run() 224 | 225 | 226 | if __name__ == '__main__': 227 | main() 228 | --------------------------------------------------------------------------------