PyQt6 Applikation Bild analysieren.

Dieses Python-Skript führt eine präzise Tiefenanalyse einer Bilddatei durch.

Es kombiniert die Suche nach versteckten URLs im gesamten Datei-Quellcode.

Zurück

Bilder aus dem Internet können eine erhebliche Gefahr darstellen und bösartige Links oder Schadcode enthalten. Während eine reine Bilddatei (wie ein normales JPEG oder PNG) den Computer beim reinen Betrachten meist nicht direkt infizieren kann, nutzen Cyberkriminelle raffinierte Methoden, um Bilder als Tarnung für Angriffe zu missbrauchen. (Quelle Microsoft)

Ein Beispiel: Bei der Bild-Steganographie (abgeleitet vom griechischen steganos für „verdeckt“ und graphein für „schreiben“) geht es im Kern darum, Informationen so in einem Medium zu verstecken, dass die Existenz der Information selbst geheim bleibt. Im Bereich der Cyberkriminalität wird diese Technik missbraucht, um Schadcode an Firewalls und Antivirenprogrammen vorbeizuschmuggeln.

Wie wird der Schadcode im Bild versteckt? Digitale Bilder bestehen aus Millionen von Pixeln, und jedes Pixel wird durch Farbwerte (Rot, Grün, Blau-RGB) definiert. Diese Werte werden in Binärcode (Nullen und Einsen) gespeichert. Angreifer nutzen meist zwei Methoden, um Code einzubauen: Die LSB-Methode (Least Significant Bit): Dies ist das häufigste Verfahren. Der Angreifer verändert das allerletzte Bit (das niederwertigste Bit) eines Farbwerts. Wenn man beispielsweise den Rotwert eines Pixels von der Binärzahl 1010110**0** in 1010110**1** ändert, ändert sich die Farbe für das menschliche Auge so minimal, dass es absolut unsichtbar bleibt. In diesen unzähligen "letzten Bits" des gesamten Bildes wird der Schadcode bitweise versteckt. Verstecken in den Metadaten: Bilder enthalten Header-Informationen und Metadaten (wie EXIF-Daten mit Kameratyp, Datum oder GPS-Koordinaten). Angreifer können Schadcode in diese Textfelder einbetten oder den Code einfach an das eigentliche Ende der Bilddatei anhängen (nach dem sogenannten "End of Image"-Marker).

Warum schlagen Antivirenscanner keinen Alarm? Klassische Antivirensoftware scannt Dateien nach bekannten Mustern (Signaturen) von Schadsoftware. Wenn ein Schadcode jedoch in die Pixelstruktur eines Bildes zerlegt und mit den echten Bilddaten vermischt ist, existiert diese Signatur im Ganzen nicht mehr. Für die Sicherheitssoftware sieht die Datei wie eine völlig legitime, unschädliche .png- oder .jpg-Datei aus. Da Bilder in der Regel keine ausführbaren Programme sind, werden sie von Scannern oft weniger streng geprüft.

Achtung! Das nachfolgende Skript kann das Problem nicht lösen! Aber zumindest werden versteckte Muster wie http:// oder https:// geortet! Struktur & Funktionsweise siehe weiter unten in dieser Beschreibung!

Diese Webseite stellt für den Quellcode Download- und Kopierfunktionen zur Verfügung. Das geht nur mit JavaScript. Wenn du mich für einen unsicheren Kantonisten hältst, verlasse diese Webseite. Alle Informationen und Angebote sind ohne Gewähr!



Python Code
#!/usr/bin/env python3 # ***************************************************************************** # Grafische Oberfläche (PyQt6) für eine Bild-Analyse # Installation der Abhängigkeit: pip install PyQt6 ; pip install pillow # pip install reportlab ; Start: python Bild_Analyse.py # (c) Hans Busche Datum: 26.07.2026 # ***************************************************************************** import sys import re import os from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QFileDialog, QLabel, QTextEdit, QSplitter, QFrame, QMessageBox) from PyQt6.QtGui import QPixmap, QFont, QIcon from PyQt6.QtCore import Qt from PIL import Image from PIL.ExifTags import TAGS from reportlab.lib.pagesizes import letter from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib import colors # Verzeichnis dieser Datei, damit das Icon unabhängig vom Startort gefunden wird BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ICON_PATH = os.path.join(BASE_DIR, "Schaefer.ico") class ImageAnalyzerApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Bild Analyse") # Icon if os.path.exists(ICON_PATH): self.setWindowIcon(QIcon(ICON_PATH)) self.resize(950, 700) # Holt die aktuellen Flags des Fensters flags = self.windowFlags() # Deaktiviert den Maximieren-Button flags &= ~Qt.WindowType.WindowMaximizeButtonHint # Deaktiviert den Minimieren-Button #flags &= ~Qt.WindowType.WindowMinimizeButtonHint self.setMinimumSize(600, 600) self.setMaximumSize(1000, 800) # Wichtig: Die geänderten Flags wieder an das Fenster übergeben self.setWindowFlags(flags) # Aktuelle Analysedaten für den Export erfassen self.current_file_path = "" self.extracted_links = [] self.extracted_text = [] self.extracted_exif = {} # Globales Stylesheet für eine dunkles Aussehen self.setStyleSheet(""" QMainWindow { background-color: #121214; } QWidget { color: #e0e0e6; font-family: 'Segoe UI', sans-serif; font-size: 13px; } QPushButton { background-color: #00adb5; color: #121214; border: none; border-radius: 4px; padding: 10px 18px; font-weight: bold; font-size: 14px; } QPushButton:hover { background-color: #00cfd8; } QPushButton:pressed { background-color: #00888f; } QPushButton:disabled { background-color: #2a2a30; color: #666670; } QPushButton#btnExport { background-color: #e23e57; color: #ffffff; } QPushButton#btnExport:hover { background-color: #ff4d6d; } QPushButton#btnExport:pressed { background-color: #b8233c; } QFrame#imageBorderFrame { border: 2px dashed #3a3a42; border-radius: 6px; background-color: #1a1a1e; } QTextEdit { background-color: #1a1a1e; border: 1px solid #2a2a30; border-radius: 4px; font-family: 'Consolas', 'Courier New', monospace; font-size: 12px; color: #00ffcc; padding: 6px; } QLabel { font-weight: bold; color: #a0a0aa; } QLabel#lblHeadline { color: #00adb5; font-size: 16px; font-weight: bold; } QScrollBar:vertical { border: none; background: #121214; width: 10px; margin: 0px; } QScrollBar::handle:vertical { background: #3a3a42; min-height: 20px; border-radius: 5px; } QScrollBar::handle:vertical:hover { background: #00adb5; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } """) self.init_ui() def init_ui(self): main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QVBoxLayout(main_widget) main_layout.setContentsMargins(15, 15, 15, 15) main_layout.setSpacing(15) # Button Layout top_layout = QHBoxLayout() self.btn_open = QPushButton(" Bild auswahlen (Windows Explorer)") self.btn_open.setCursor(Qt.CursorShape.PointingHandCursor) self.btn_open.clicked.connect(self.open_image) self.btn_export = QPushButton(" PDF-Bericht exportieren") self.btn_export.setObjectName("btnExport") self.btn_export.setCursor(Qt.CursorShape.PointingHandCursor) self.btn_export.setEnabled(False) self.btn_export.clicked.connect(self.export_to_pdf) top_layout.addWidget(self.btn_open) top_layout.addWidget(self.btn_export) main_layout.addLayout(top_layout) # Splitter für flexible Anordnung nebeneinander splitter = QSplitter(Qt.Orientation.Horizontal) main_layout.addWidget(splitter) # Linker Bereich: Bildcontainer self.img_frame = QFrame() self.img_frame.setObjectName("imageBorderFrame") img_frame_layout = QVBoxLayout(self.img_frame) img_frame_layout.setContentsMargins(10, 10, 10, 10) self.lbl_image = QLabel("Kein Bild geladen") self.lbl_image.setAlignment(Qt.AlignmentFlag.AlignCenter) img_frame_layout.addWidget(self.lbl_image) splitter.addWidget(self.img_frame) # Rechter Bereich: Analyse Text right_widget = QWidget() right_layout = QVBoxLayout(right_widget) right_layout.setContentsMargins(0, 0, 0, 0) right_layout.setSpacing(10) # Gruppe 1: Versteckte Links right_layout.addWidget(QLabel("Gefundene Links (im Bild & Dateiende):")) self.txt_links = QTextEdit() self.txt_links.setReadOnly(True) right_layout.addWidget(self.txt_links) # Gruppe 2: Text Fragmente right_layout.addWidget(QLabel("Extrahiert lesbare Textfragmente (Strings):")) self.txt_strings = QTextEdit() self.txt_strings.setReadOnly(True) right_layout.addWidget(self.txt_strings) # Gruppe 3: EXIF Meta-Data right_layout.addWidget(QLabel("EXIF Metadaten-Analyse:")) self.txt_exif = QTextEdit() self.txt_exif.setReadOnly(True) right_layout.addWidget(self.txt_exif) splitter.addWidget(right_widget) # Die Standardgrößen der Splitter (50 % / 50 %) ausgleichen splitter.setSizes([600, 600]) def open_image(self): file_path, _ = QFileDialog.getOpenFileName( self, "Bilddatei auswaehlen", "", "Bilder (*.png *.jpg *.jpeg *.bmp *.gif)" ) if file_path: self.current_file_path = file_path # Scaled image display pixmap = QPixmap(file_path) self.lbl_image.setPixmap(pixmap.scaled( self.lbl_image.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation )) self.analyze_image_file(file_path) self.btn_export.setEnabled(True) def analyze_image_file(self, file_path): # Byte-Daten für Regex-Suchvorgänge lesen with open(file_path, "rb") as f: binary_data = f.read() # 1. URL-Suche (eingebettete und angehängte Zeichenfolgen) url_pattern = re.compile(b'https?://[a-zA-Z0-9./?=&_-]+') found_raw = url_pattern.findall(binary_data) self.extracted_links = sorted(list(set([link.decode('utf-8', errors='ignore') for link in found_raw]))) if self.extracted_links: self.txt_links.setText("\n".join(self.extracted_links)) else: self.txt_links.setText("Keine versteckten Links gefunden.") # 2. Suche nach zusammenhängenden Textzeichenfolgen (Länge 4+) text_pattern = re.compile(b'[a-zA-Z0-9 .,!?;:_\-()]{4,}') strings_raw = text_pattern.findall(binary_data) self.extracted_text = [s.decode('utf-8', errors='ignore').strip() for s in strings_raw if s.strip()] if self.extracted_text: self.txt_strings.setText("\n".join(self.extracted_text[:200])) # display top 200 lines else: self.txt_strings.setText("Keine klaren Textfragmente im Binärcode extrahiert.") # 3. Parsen von EXIF-Metadaten mit Pillow self.extracted_exif = {} try: with Image.open(file_path) as img: exif_data = img._getexif() if exif_data: for tag_id, value in exif_data.items(): tag_name = TAGS.get(tag_id, tag_id) # Bereinigen Sie Binärwerte, um sie druckbar zu machen. if isinstance(value, bytes): value = value.decode('utf-8', errors='ignore') self.extracted_exif[str(tag_name)] = str(value) except Exception as e: self.extracted_exif = {"Fehler": f"Metadaten konnten nicht gelesen werden: {str(e)}"} if self.extracted_exif: exif_lines = [f"{k}: {v}" for k, v in self.extracted_exif.items()] self.txt_exif.setText("\n".join(exif_lines)) else: self.txt_exif.setText("Keine EXIF-Metadaten in dieser Datei vorhanden.") def export_to_pdf(self): if not self.current_file_path: return save_path, _ = QFileDialog.getSaveFileName( self, "Bild-Analyse PDF-Bericht speichern", "Bild_Bericht.pdf", "PDF-Dateien (*.pdf)" ) if not save_path: return try: doc = SimpleDocTemplate(save_path, pagesize=letter, title="Bildanalyse-Bericht") styles = getSampleStyleSheet() # Individuelle, klare Stile für das PDF title_style = ParagraphStyle( 'ReportTitle', parent=styles['Heading1'], fontSize=22, spaceAfter=12, textColor=colors.HexColor('#121214') ) h2_style = ParagraphStyle( 'ReportH2', parent=styles['Heading2'], fontSize=14, spaceBefore=14, spaceAfter=6, textColor=colors.HexColor('#00adb5') ) body_style = ParagraphStyle( 'ReportBody', parent=styles['BodyText'], fontSize=10, spaceAfter=4, textColor=colors.HexColor('#2c3e50') ) code_style = ParagraphStyle( 'ReportCode', parent=styles['Code'], fontSize=9, leading=11, textColor=colors.HexColor('#1a1a24'), backColor=colors.HexColor('#f4f4f6'), borderPadding=5 ) story = [] # Title Elemente story.append(Paragraph("Bildanalyse-Bericht", title_style)) story.append(Paragraph(f"Analysierte Datei: {os.path.basename(self.current_file_path)}", body_style)) story.append(Paragraph(f"Vollstandiger Pfad: {self.current_file_path}", body_style)) story.append(Spacer(1, 15)) # 1. Links story.append(Paragraph("1. Versteckte Links / URLs", h2_style)) if self.extracted_links: links_text = "
".join(self.extracted_links) story.append(Paragraph(links_text, code_style)) else: story.append(Paragraph("Keine versteckten Links oder URLs detektiert.", body_style)) # 2. EXIF Meta story.append(Paragraph("2. Extrahierte EXIF-Metadaten", h2_style)) if self.extracted_exif and "Fehler" not in self.extracted_exif: exif_table_data = [[Paragraph("Tag", body_style), Paragraph("Wert", body_style)]] for k, v in self.extracted_exif.items(): exif_table_data.append([Paragraph(str(k), body_style), Paragraph(str(v), body_style)]) t = Table(exif_table_data, colWidths=[150, 350]) t.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,0), colors.HexColor('#eeeeee')), ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#cccccc')), ('VALIGN', (0,0), (-1,-1), 'TOP'), ('PADDING', (0,0), (-1,-1), 5), ])) story.append(t) else: msg = self.extracted_exif.get("Fehler", "Keine EXIF-Metadaten verfugbar.") story.append(Paragraph(msg, body_style)) # 3. Text Strings story.append(Paragraph("3. Extrahierte Text-Fragmente (Auszug)", h2_style)) if self.extracted_text: # Begrenzen Sie die Textzeichenfolgen im Bericht auf die Top 50, um riesige Dateien zu vermeiden. strings_text = "
".join(self.extracted_text[:50]) story.append(Paragraph(strings_text, code_style)) else: story.append(Paragraph("Keine lesbaren Textfragmente im Binärcode gefunden.", body_style)) doc.build(story) QMessageBox.information(self, "Erfolg", "Der PDF-Bericht wurde erfolgreich generiert!") except Exception as e: QMessageBox.critical(self, "Fehler", f"PDF-Export fehlgeschlagen: {str(e)}") if __name__ == "__main__": app = QApplication(sys.argv) window = ImageAnalyzerApp() window.show() sys.exit(app.exec())


Dieses Python-Skript führt eine präzise Tiefenanalyse einer Bilddatei durch. Es kombiniert die Suche nach versteckten URLs im gesamten Datei-Quellcode (mit speziellem Fokus auf das Dateiende) sowie das Auslesen von URLs aus den unsichtbaren EXIF-Metadaten.

Über QFileDialog.getOpenFileName öffnet sich der Windows-Dateiexplorer, um gängige Bildformate (PNG, JPEG, BMP, GIF) auszuwählen. Visuelle Bildanzeige: Das geladene Bild wird skaliert und formgetreu auf der linken Seite des Anwendungsfensters dargestellt.

Versteckte Links: Liest die Datei auf Binärebene (rb-Modus). Ein regulärer Ausdruck sucht nach Mustern wie http:// oder https://. Dies erfasst URLs, die mittels Steganografie in die Pixeldaten eingebettet wurden, sowie Daten, die an das Dateiende (EOF - End of File) angehängt wurden.

Text-Visualisierung: Scannt die Binärdaten nach fortlaufenden, menschenlesbaren ASCII/UTF-8-Zeichenketten (Strings-Extraktion). Gefundene Textfragmente, Metadaten oder versteckte Nachrichten werden extrahiert und formatiert ausgegeben.

EXIF-Metadaten-Analyse: Integriert die automatisierte Auslesung von Kamera-Tags, Zeitstempeln und Geräte-Informationen mittels Python-Pillow-Bibliothek.

Drei Detektive! Eine kurze Beschreibung mit einfachen Worten: Sobald das Bild geladen ist, läuft im Hintergrund sofort die dreifache Überprüfung:
Detektiv 1: Die Link-Suche (Versteckte URLs) Bilder bestehen aus Millionen von Farbpunkten (Pixeln). Man kann Text oder Links so in diesen Punkten verstecken, dass das Auge sie nicht sieht. Das Skript liest das Bild als reinen Computercode (Binärdaten) und sucht nach typischen Mustern wie http:// oder https://. Es findet Links, die irgendwo mittendrin oder ganz frech an das eigentliche Dateiende (EOF) angehängt wurden.

Detektiv 2: Die Text-Suche (Strings-Extraktion). Manchmal verstecken Leute geheime Nachrichten oder Kommentare im Code eines Bildes. Das Skript scannt die Datei nach lesbaren Buchstabenketten. Sobald mindestens 4 normale Buchstaben oder Zahlen am Stück auftauchen (z. B. "Geheimnis123"), filtert das Skript sie heraus und zeigt sie dir an.

Detektiv 3: Der digitale Fingerabdruck (EXIF-Metadaten). Wenn du ein Foto mit dem Smartphone oder einer Kamera machst, speichert das Gerät unsichtbare Zusatzdaten direkt im Bild ab. Das Skript nutzt die Bibliothek Pillow, um dieses digitale Tagebuch auszulesen. Es zeigt dir (falls vorhanden) das Kameramodell, das genaue Datum und die Uhrzeit der Aufnahme an.

Jetzt aber zurück zum Thema PyQt6-Festertechnik! "Aber flott, Donnerwetter"!

Der fensterübergreifende Aufbau der PyQt6-Oberfläche basiert auf einer verschachtelten Box-Struktur (Layouts), die wie unsichtbare Schienen funktionieren. Sie sorgen dafür, dass sich alle Elemente beim Vergrößern des Fensters automatisch anpassen. Hier ist die PyQt6-Struktur im Detail von außen nach innen aufgeschlüsselt:

Das Fundament (QMainWindow & QWidget)QMainWindow: Das Hauptfenster-Objekt. Es liefert das Standard-Rahmengerüst für Windows-Anwendungen (inklusive der Titelleiste, Minimieren-/Maximieren- Knöpfen und der Fenstergröße).central_widget (QWidget): Ein leeres Basis-Element. PyQt6 verlangt zwingend, dass der gesamte Inhalt des Fensters in ein einziges zentrales Element gepackt wird (self.setCentralWidget).

Die Hauptaufteilung (QHBoxLayout & QSplitter): Innerhalb des zentralen Elements liegt ein horizontales Layout (QHBoxLayout). Dieses teilt das Fenster in eine linke und eine rechte Hälfte: Linke Seite (Bildbereich): Ein vertikales Layout (QVBoxLayout). Es stapelt die beiden oberen Aktions-Buttons (Laden & PDF-Export) über der großen Bildanzeige (QLabel).Mitte (QSplitter): Der unsichtbare Trenner. Ein QSplitter sorgt dafür, dass du die Trennlinie zwischen der Bildvorschau (links) und den Textboxen (rechts) mit der Maus verschieben kannst. Rechte Seite (Datenbereich): Ein vertikales Layout (QVBoxLayout), das die drei Ergebnis-Fenster sauber untereinander stapelt.

Die Ergebnis-Fenster (QTextEdit): Die drei Analyse-Bereiche (Links, Strings, EXIF) bestehen jeweils aus einem QTextEdit. Diese Widgets sind darauf ausgelegt, große Mengen Text anzuzeigen. PyQt6 spendiert diesen Boxen automatisch Scrollbalken, sobald der Text länger als das Fenster ist.Sie sind über .setReadOnly(True) gesperrt, damit du die Ergebnisse zwar kopieren, aber nicht versehentlich überschreiben kannst.

Das Design-Kleid (setStyleSheet): Das gesamte Aussehen wird über QSS (Qt Style Sheets) gesteuert. Das ist das PyQt6-Äquivalent zu CSS im Webdesign. Jedes Element bekommt hierbei Farbwerte für den normalen Zustand (z. B. dunkles Grau für den Hintergrund). Über spezielle Befehle wie :hover (Maus zieht darüber) oder :pressed (Knopf wird geklickt) weiß PyQt6, wann es die Button-Farbe dynamisch in Cyan ändern muss.

Die Signal-Slot-Logik ist das zentrale Nervensystem von PyQt6. Sie steuert, wie die grafische Oberfläche (die Buttons) mit der Logik deines Python-Codes (den Analyse-Funktionen) kommuniziert. In einfachen Worten: Ein Signal ist das Ereignis, und der Slot ist die Reaktion darauf.

Das Grundprinzip: Wer funkt wen an? Das Signal (Der Auslöser): Jedes Mal, wenn ein Benutzer eine Aktion ausführt (z. B. auf einen Button klickt, ein Textfeld ändert oder das Fenster schließt), „funkt“ das PyQt6-Element ein Signal in die Anwendung. Der Slot (Der Empfänger): Ein Slot ist eine ganz normale Python-Funktion in unserem Code, die darauf wartet, ausgeführt zu werden, sobald das Signal ertönt. Die Verbindung wird im Code über den Befehl .connect() hergestellt.


PyQt6 Bild Analyse
Gib mal "was macht ns.adope.com in einer Bilddatei?" in deinem Browser Suchfeld ein!
Zurück