Menu

Un simple Scanner de Ports avec Python et Tkinter

Port Scanner

Introduction

Cet article est le second volet du tutoriel consacré à la création d’un scanner de ports avec Tkinter. Dans le premier article, nous avions créé un scanner de ports avec le module socket de Python. Nous avions aussi utilisé le multithreading pour améliorer les performances de l’opération de scan.

Attention: Je rappelle qu’il est formellement interdit de scanner les ports d’un hôte sans sa permission explicite. Pour tester le scanner, faites-le au niveau de votre propre réseau. Vous êtes entièrement responsables de l’usage que vous faites de ce scanner de ports.

Dans ce deuxième article, nous allons voir comment utiliser le module Tkinter afin de créer une interface graphique pour notre scanner de ports. Nous pourrons ensuite produire un exécutable de notre application à l’aide du module PyInstaller.

Le code source final de ce projet est accessible sur GitHub.

Scanner de ports avec Tkinter

Tkinter est l’une des nombreuses bibliothèques graphiques utilisées dans Python. C’est une bibliothèque facile à utiliser qui permet de créer des interfaces graphiques de façon simple et rapide.

Pour l’instant nous avons un scanner de ports en mode ligne de commande. On va utiliser le module Tkinter pour créer une version graphique de notre scanner. Avec ce dernier, l’utilisateur pourra saisir sa cible ainsi que la liste de ports qu’il veut scanner avant de lancer un scan. L’application permettra aussi d’afficher les résultats d’un scan et les enregistrer dans un fichier texte.

Pour réaliser notre application graphique avec Tkinter, nous allons :

  • créer et mettre en place des éléments graphiques avec lesquels l’utilisateur peut interagir et qu’on appelle communément widgets ;
  • écrire les méthodes et les fonctions associées aux widgets ;
  • faire appel à une boucle d’évènements, qui va nous permettre de gérer les actions de l’utilisateur et les évènements qui se produisent au niveau de l’interface graphique.

Dans ce tutoriel, je vous propose de faire appel à la programmation orientée objet pour créer notre application avec Tkinter afin d’avoir un code mieux organisé. Pour ce faire, on va travailler sur un nouveau fichier Python qui aura le squelette de code suivant :

import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
if __name__ == "__main__":
app = App()
app.mainloop()

Après avoir importé le module Tkinter, on définit une classe App qui hérite de la classe tk.Tk. A l’intérieur de la classe, on définit la méthode __init__() dans laquelle on appelle la méthode __init__() de la classe tk.TK.

On crée ensuite l’objet app en instanciant la classe App et on appelle la boucle d’évènements mainloop(), associée à l’objet app, pour afficher la fenêtre de l’application.

Pour écrire la suite de l’application, on va d’abord construire l’interface, en positionnant les différents widgets la composant, puis mettre en place la logique via les différentes méthodes de notre scanner.

Construire l’interface de l’application

Nous allons créer une interface graphique qui aura cet aspect :

Dans cette interface, on insère des zones de saisies pour donner la possibilité à un utilisateur d’entrer une cible et trois choix de listes de ports :

  • une liste par défaut pour les ports communs (liste restreinte de ports connus) ;
  • une liste où l’on spécifie les ports qu’on veut tester (séparés par des virgules) ;
  • une plage de ports bornée par une valeur minimale et une valeur maximale.

On aura aussi un bouton pour lancer le scanner, une zone de texte dans laquelle on affichera les résultats des scans ainsi qu’un bouton pour enregistrer les résultats dans un fichier texte.

Agencement des widgets

TKinter offre trois méthodes pour gérer le positionnement des éléments graphiques dans une interface :

  • pack() : est utilisé pour agencer les widgets dans des conteneurs horizontalement et verticalement ;
  • place() : permet de placer les éléments graphiques en utilisant des coordonnées relatives au coin supérieur gauche de la fenêtre (coordonnées absolues) ;
  • grid() : place les widgets dans une grille (tableau à deux dimensions).

Nous allons utiliser la méthode grid() pour agencer nos éléments comme suit :

On place chaque widget dans une cellule du tableau, en utilisant différentes options de grid(), telles que :

  • column, row : ces deux options représentent les coordonnées d’un élément dans la grille (respectivement la colonne et la ligne) ;
  • columnspan : nombre de colonnes qu’occupe un widget ;
  • sticky : permet de positionner l’objet dans une cellule dont les bords sont associés aux quatre points cardinaux (N, S, W, E) ;
  • padx, pady : respectivement les marges externes horizontales et verticales.

Dans notre programme, le positionnement des éléments de l’interface graphique se traduit donc comme suit :

import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self):
super().__init__()
# Default : common ports
self.common_ports = [20, 21, 22, 23, 25, 53, 80, 110, 443]
# Configure the Root Window
self.title("My Port Scanner")
self.geometry("820×550")
self.resizable(0, 0)
# Set default Font
self.def_font = tk.font.nametofont("TkDefaultFont")
self.def_font.config(family="Segoe Script", size=13, weight=font.BOLD)
# Port Scanner title
ttk.Label(self, text="My Port Scanner", font=("Segoe Script", 17, "bold")) \
.grid(column=0, row=0, columnspan=4)
# Label Hostname
ttk.Label(self, text="Enter your target : ").grid(column=0, row=1)
# Entry Hostname
self.hostname = ttk.Entry(self, font="TkDefaultFont")
self.hostname.insert(tk.END, 'localhost')
self.hostname.grid(column=1, row=1, sticky=tk.EW, columnspan=3)
# Get ports : common, list or range
self.option_port_common = ttk.Radiobutton(self, text="Common Ports", value=1)
self.option_port_common.grid(column=0, row=2, sticky=tk.W)
self.option_port_list = ttk.Radiobutton(self, text="Ports List", value=2)
self.option_port_list.grid(column=0, row=3, sticky=tk.W)
self.option_port_range = ttk.Radiobutton(self, text="Ports Range", value=3)
self.option_port_range.grid(column=0, row=4, sticky=tk.W)
# Port entries
self.port_common_entry = ttk.Entry(self, font="TkDefaultFont")
self.port_common_entry.insert(0, str(self.common_ports)[1:-1])
self.port_common_entry.config(state='readonly')
self.port_common_entry.grid(column=1, row=2, sticky=tk.EW, columnspan=3)
self.port_list_entry = ttk.Entry(self, font="TkDefaultFont")
self.port_list_entry.config(state='disabled')
self.port_list_entry.grid(column=1, row=3, sticky=tk.EW, columnspan=3)
self.port_range_entry_1 = ttk.Entry(self, font="TkDefaultFont")
self.port_range_entry_1.grid(column=1, row=4, sticky=tk.W)
self.port_range_entry_1.config(state='disabled')
ttk.Label(self, text="-").grid(column=2, row=4, sticky=tk.W)
self.port_range_entry_2 = ttk.Entry(self, font="TkDefaultFont")
self.port_range_entry_2.config(state='disabled')
self.port_range_entry_2.grid(column=3, row=4, sticky=tk.W)
# Port Options
self.port_entries = [self.port_common_entry,
self.port_list_entry,
self.port_range_entry_1, self.port_range_entry_2]
# Scan Button
scan_button = ttk.Button(self, text="Scan Target")
scan_button.grid(column=1, row=5, columnspan=3, sticky=tk.EW)
# Results Label
ttk.Label(self, text="Results : ").grid(column=0, row=7, sticky=tk.EW)
# Results Area
self.results_area = scrolledtext.ScrolledText(self, width=30, height=5, font="TkDefaultFont")
self.results_area.grid(column=1, row=7, columnspan=3, pady=10, padx=10, sticky=tk.EW)
self.results_area.config(state='disabled')
# Save Results Button
self.save_button = ttk.Button(self, text="Save Results")
self.save_button.grid(column=1, row=9, columnspan=3, sticky=tk.EW)
self.save_button.config(state='disabled')
# Set default padding for all widgets
for widget in self.winfo_children():
if widget.grid_info()['column'] == 0:
widget.grid(padx=25)
widget.grid(pady=5)
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Importer les sous-modules Tkinter

En premier lieu, on importe les sous-modules de Tkinter dont on a besoin :

  • ttk : utilisé pour habiller la plupart des widgets Tkinter avec des thèmes et des styles modernes ;
  • font : permet de spécifier la police d’écriture des éléments textuels de l’interface ;
  • scrolledtext : permet de créer un objet texte multilignes avec une barre de défilement latérale.

Gérer la résolution

Lorsqu’on travaille avec un écran haute résolution, il est possible que les éléments graphiques de l’application Tkinter aient un aspect flou. Pour éviter cela, il faut modifier la valeur des pixels par pouce ou DPI (“Dot Per Inch”, en anglais). On importe alors le module ctypes qui nous fournit la fonction SetProcessDpiAwareness(), permettant d’améliorer le rendu de l’interface.

Organiser les éléments graphiques

Les éléments de la fenêtre de l’application seront décrits au niveau de la méthode __init()__ de la classe App. On aura des attributs comprenant des variables ainsi que les widgets de l’interface. S’en suivront les différentes méthodes qui seront associées aux éléments de l’interface et qui permettront de faire fonctionner notre scanner.

Dans notre méthode __init()__, on initialise les paramètres de base de la fenêtre qui nous servira d’interface. On définit le titre, la taille de l’interface ainsi qu’une police d’écriture.

Pour positionner les différent éléments de l’interface du scanner de ports, on travaille en mode grille avec le gestionnaire de positionnement grid() de Tkinter.

Pour placer nos widgets, on procède ligne par ligne. Dans la première colonne de chaque ligne, on dispose les étiquettes (Labels, en anglais) définissant les autres widgets. Les champs de saisie, les boutons et la zone de texte dédiée aux résultats des scans, sont placés sur les colonnes suivantes dans chaque ligne.

Le premier champs de saisie correspond à la cible du scan. On lui donne une valeur par défaut : localhost.

On donne le choix de liste de ports à l’aide du widget RadioButton. A chaque bouton radio, seront associés le ou les champs de saisie correspondants.

Le premier choix correspond à une liste réduite de ports connus ou communs. On y mettra la liste définie plus haut dans la variable common_ports. Pour que ce champs ne soit pas modifiable, on lui assigne l’état readonly. Puisqu’il y a une liste de ports par défaut, les autres champs seront désactivés/grisés (state='disabled') au lancement de l’application. On met les champs dans une liste port_entries dont on aura besoin plus tard.

On crée aussi le bouton scan_button qui nous permettra de lancer le scan de ports et on utilise la classe ScrolledText, pour instancier le widget results_area, dans lequel on affichera les résultats du scan.

Ensuite, on a un autre bouton save_button, qui donnera la main à l’utilisateur pour qu’il puisse enregistrer ses résultats. Tant qu’on ne lance pas de scan, ce bouton reste désactivé.

Enfin, on améliore l’aspect général de l’interface en espaçant les widgets entre eux avec les paramètres padx (padding horizontal) et pady (padding vertical).

Ecrire la logique de l’application

Pour scanner les ports d’une cible, il faut fournir le nom d’hôte ou l’adresse IP de celle-ci ainsi qu’une liste de ports à parcourir. Afin de mettre en place la logique de l’application, nous aurons à écrire ou réécrire les méthodes suivantes :

  • get_target() : récupère l’adresse IP de la cible ;
  • select_ports() : permet de choisir une liste de ports ;
  • get_port_list() : récupère la liste de ports choisie ;
  • scan_port() : retourne le scan d’un port pour une cible donnée ;
  • port_scanner() : renvoie le résultat du scan d’une liste de ports ;
  • write_file() : permet d’écrire les résultats dans un fichier texte ;
  • save_results() : permet d’enregistrer le fichier des résultats du scan.

Dans notre script, nous allons définir ces différentes méthodes dans cet ordre :

import socket
import threading
from datetime import datetime
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from tkinter.filedialog import asksaveasfilename
from ctypes import windll
class App(tk.Tk):
def __init__(self): …
# Get Target Value
def get_target(self): …
# Enable Ports Selection
def select_ports(self, v): …
# Return Selected Ports List
def get_port_list(self): …
# Scan a Single Port
def scan_port(self, target, port): …
# Port Scanner
def port_scanner(self): …
# Write results in file
def write_file(self, file): …
# Save results in file
def save_results(self): …
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Récupérer la cible

Avant de lancer un scan, on doit renseigner le nom de l’hôte ou son adresse IP et choisir une liste de ports à scanner. Au départ, le nom d’hôte et la liste de ports ont des valeurs par défaut (respectivement la valeur localhost et la liste common_ports). On commence par importer le module socket. Pour récupérer le nom d’hôte saisi par l’utilisateur, on reprend la fonction get_target(), vue dans le premier article de ce tutoriel, pour la modifier.

import socket
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self):
# Get default background
self.defaultbg = self.cget('bg')
# Get Target Value
def get_target(self):
try:
target = socket.gethostbyname(self.hostname.get())
except socket.gaierror:
return False
except socket.error:
return False
except UnicodeError:
return False
else:
ttk.Label(self, text=" Scan target : " + target, background=self.defaultbg) \
.grid(column=1, row=6, columnspan=3, sticky=tk.EW)
return target
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Après avoir récupéré la cible, on gère les différents types d’erreurs de façon plus précise. Si la saisie est correcte, on affiche la cible via un widget de type Label. Pour avoir un affichage plus joli, on utilise la valeur par défaut de la couleur de font de l’application. Ladite valeur est définie plus haut avec les autres attributs via la variable defaultbg.

Quant à la valeur de la cible, elle sera récupérée dans une variable de la fonction port_scanner() qu’on verra plus tard.

Définir les ports à scanner

Sélectionner une liste

Pour choisir la liste de ports, on a trois boutons radio associés à des champs de saisie. On a donné aux boutons radio des valeurs (1, 2 et 3). Il faut récupérer ces valeurs à l’aide d’une variable. On déclare la variable avec les autres attributs et on lui donne une valeur par défaut var = tk.IntVar(value=1). La variable est partagée entre les trois boutons radio via l’option variable=self.var. C’est un paramètre qui nous permet de saisir la liste de ports dans les champs correspondants.

Pour chaque bouton radio, on associe à l’option command, une fonction lambda pour passer la variable en argument à la fonction select_ports(), qui nous servira à sélectionner la liste de ports.

import socket
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self):
# Default : common ports (shared variable for the radio buttons)
self.var = tk.IntVar(value=1)
# Get ports : common, list or range
self.option_port_common = ttk.Radiobutton(self, text="Common Ports", variable=self.var, value=1,
command=lambda v=self.var: self.select_ports(v))
self.option_port_common.grid(column=0, row=2, sticky=tk.W)
self.option_port_list = ttk.Radiobutton(self, text="Ports List", variable=self.var, value=2,
command=lambda v=self.var: self.select_ports(v))
self.option_port_list.grid(column=0, row=3, sticky=tk.W)
self.option_port_range = ttk.Radiobutton(self, text="Ports Range", variable=self.var, value=3,
command=lambda v=self.var: self.select_ports(v))
self.option_port_range.grid(column=0, row=4, sticky=tk.W)
# Get Target Value
def get_target(self): …
# Enable Ports Selection
def select_ports(self, v):
if v.get() == 1:
for p in self.port_entries:
if p != self.port_common_entry:
p.configure(state='disabled')
else:
p.configure(state='readonly')
if v.get() == 2:
for p in self.port_entries:
if p != self.port_list_entry:
p.configure(state='disabled')
else:
p.configure(state='normal')
if v.get() == 3:
for p in self.port_entries:
if p != self.port_common_entry and p != self.port_list_entry:
p.configure(state='normal')
else:
p.configure(state='disabled')
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

La fonction select_port() permet d’activer les champs relatifs à la sélection. Pour cela, on récupère la variable passée en argument, on parcours la liste de champs de saisie et on modifie l’état de ces champs selon la valeur de la variable :

  • lorsque celle-ci est égale à 1, la liste de ports sera la liste par défaut common_ports et les autres champs sont désactivés ;
  • dans le cas où la variable est égale à 2, on n’active que le champs de saisie port_list_entry et les autres sont désactivés ;
  • si elle est égale à 3, on n’active que les champs port_range_entry_1 et port_range_entry_2.

Récupérer une liste de ports

C’est la fonction get_port_list() qui nous permettra de récupérer la liste de ports choisie ou saisie par l’utilisateur.

import socket
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self): …
# Get Target Value
def get_target(self): …
# Enable Ports Selection
def select_ports(self, v): …
# Return Selected Ports List
def get_port_list(self):
ports = list()
port_selection = self.var.get()
if port_selection == 1:
ports = self.common_ports
if port_selection == 2 and self.port_list_entry.get() != '':
try:
ports = list(map(int, self.port_list_entry.get().split(',')))
except ValueError:
return False
if port_selection == 3 and self.port_range_entry_1.get().isdigit() and self.port_range_entry_2.get().isdigit():
if self.port_range_entry_1.get() < self.port_range_entry_2.get():
try:
ports = list(range(int(self.port_range_entry_1.get()), int(self.port_range_entry_2.get()) + 1))
except ValueError:
return False
else:
return False
if ports:
valid_ports = all(p in list(range(0, 65535)) for p in ports)
if valid_ports:
return ports
else:
return False
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

On utilise la liste ports pour récupérer la liste de ports relative au choix de l’utilisateur port_selection :

  • si le choix est égal à 1, la liste ports prend la valeur common_ports ;
  • lorsque le choix est égal à 2, on teste si la saisie est correcte et on assigne les valeurs transmises sous forme de liste à la variable ports ;
  • dans le cas où le choix est égal à 3, on teste que les valeurs saisies sont des nombres et qu’ils ont été saisis dans l’ordre avant de les utiliser pour construire la liste ports ;

Enfin, on vérifie que les ports de la liste sont bien inclus dans la page de ports valide [0, 65535].

Scanner de ports

Scanner un seul port

Pour l’instant nous avons les fonctions qui permettent de récupérer les données nécessaires pour lancer le scan. On va maintenant initialiser les variables dont on a besoin pour récupérer les données saisies par l’utilisateur (self.target et self.ports) ainsi que les résultats du scan (self.results).

On modifie la fonction scan_port(), vue en première partie et qui permet de scanner un port unique, on récupère le résultat du scan de port et on l’ajoute à la liste self.results.

import socket
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self):
# Initial values for target, ports and results list
self.target = ''
self.ports = list()
self.results = list()
# Scan a Single Port
def scan_port(self, target, port):
# Create a socket object
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
test = s.connect_ex((target, port))
if test == 0:
result = f' Port {port} is [open] \n'
self.results.append(result)
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Scanner une liste de ports

On reprend aussi la méthode port_scanner(). On s’assure d’abord d’importer les modules threading et datetime. Dans cette méthode, on appelle les fonctions get_target() et port_list() et on récupère leurs résultats respectivement dans les variables self.target et self.ports.

On lance ensuite le scan en simultané des ports de la cible et on active (state='normal') la zone de texte self.results_area et on y affiche les résultats du scan.

Lorsque le scan se passe bien, on affiche à l’aide d’une étiquette un message indiquant le bon déroulement de l’opération ainsi que le temps que ça a pris. Dans le cas contraire, on affiche un message d’erreur.

Pour permettre à l’utilisateur de lancer le scan, on associe la commande command=self.port_scanner qui appelle la méthode port_scanner() au bouton scan_button.

import socket
import threading
from datetime import datetime
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self):
# Scan Button
scan_button = ttk.Button(self, text="Scan Target", command=self.port_scanner)
scan_button.grid(column=1, row=5, columnspan=3, sticky=tk.EW)
# Scan a Single Port
def scan_port(self, target, port): …
# Port Scanner
def port_scanner(self):
self.target = self.get_target()
self.ports = self.port_list()
if self.target and self.ports:
thread_list = list()
self.results.clear()
start_time = datetime.now()
for port in self.ports:
scan = threading.Thread(target=self.scan_port, args=(self.target, port))
thread_list.append(scan)
scan.daemon = True
scan.start()
for scan in thread_list:
scan.join()
self.results_area.configure(state='normal')
self.results_area.delete('1.0', tk.END)
for r in self.results:
self.results_area.insert(tk.INSERT, r)
self.results_area.configure(state='disabled')
end_time = datetime.now()
ttk.Label(self, text=" Scanning completed in " + str(end_time – start_time),
foreground='green', background='light green') \
.grid(column=1, row=8, columnspan=3, sticky=tk.EW)
self.save_button.config(state='normal')
else:
ttk.Label(self, text=" Oops ! Looks like you did something wrong !",
foreground='red', background='pink') \
.grid(column=1, row=8, columnspan=3, sticky=tk.EW)
self.save_button.config(state='disabled')
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Enregistrer les résultats du scan

Si on veut garder les résultats du scan, on peut les enregistrer dans un fichier. On commence par créer la méthode write_file() qui permettra d’écrire les résultats dans un fichier. Puis, on fait appel à celle-ci dans la méthode save_results() pour permettre à l’utilisateur d’enregistrer le fichier créé.

Ecrire les résultats

Avec la méthode write_file(), on inscrit un entête comprenant un titre, l’adresse IP de la cible et la liste des ports scannés, puis on affiche le nombre et la liste des ports ouverts.

import socket
import threading
from datetime import datetime
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from ctypes import windll
class App(tk.Tk):
def __init__(self):
# Write results in file
def write_file(self, file):
with open(file, 'w') as f:
f.write("Port scanner results : \n")
f.write("-"*22 + "\n")
f.write(f" Target : \t {self.target} \n")
if self.var.get() != 3:
f.write(f" Ports : \t {str(self.ports)} \n")
else:
f.write(f" Ports : \t "
f"[{str(self.port_range_entry_1.get())} – {str(self.port_range_entry_2.get())}]\n")
f.write(f"\n Results : \t {str(len(self.results))}/{str(len(self.ports))} \n")
f.write(f"{str(self.results_area.get('1.0', tk.END))}")
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Enregistrer les résultats

Pour écrire la méthode save_results(), on importe la méthode asksaveasfilename() qui donne la main à l’utilisateur pour qu’il choisisse l’emplacement d’enregistrement d’un fichier. On formate au préalable le nom du fichier et on précise son extension par défaut, avant de faire appel à la méthode write_file() pour écrire les résultats du scan dans le fichier.

Pour utiliser cette méthode via l’interface, on associe la commande command=self.save_results au bouton save_button.

import socket
import threading
from datetime import datetime
import tkinter as tk
from tkinter import ttk, font, scrolledtext
from tkinter.filedialog import asksaveasfilename
from ctypes import windll
class App(tk.Tk):
def __init__(self):
# Save Results Button
self.save_button = ttk.Button(self, text="Save Results", command=self.save_results)
self.save_button.grid(column=1, row=9, columnspan=3, sticky=tk.EW)
self.save_button.config(state='disabled')
# Write results in file
def write_file(self, file): …
# Save results in file
def save_results(self):
file = asksaveasfilename(defaultextension=".txt",
initialfile=f'Scan_Results_{datetime.now().strftime("%Y%m%d-%H%M%S")}.txt')
if file:
self.write_file(file)
if __name__ == "__main__":
app = App()
windll.shcore.SetProcessDpiAwareness(1)
app.mainloop()

Créer un exécutable avec PyInstaller

Avant de conclure, on transforme notre script en fichier exécutable à l’aide du module PyInstaller, qu’il nous faut installer au préalable. Pour cela, on utilise la commande ci-dessous au niveau de l’invite de commande :

> pip install pyinstaller

Afin de créer un fichier exécutable unique, on utilise la commande suivante :

> pyinstaller --onefile --noconsole portscanner.py

Cela permet de créer le fichier portscanner.exe dans le dossier dist à la racine du projet.

Tester l’application

Vous pouvez utiliser les valeurs par défaut et scanner votre propre machine, mais aussi prendre comme cible d’autres machines de votre réseau local, comme votre routeur par exemple. En général, ce dernier a pour adresse IP 192.168.0.1 ou 192.168.1.1. Vous pouvez ensuite sélectionner la liste de ports qui vous convient et lancer le scan.

Vous aurez ensuite la possibilité d’enregistrer les résultats dans un fichier comme ceci :

Ouvrez le fichier pour visualiser les résultats du scan :

Et voilà! Vous avez créé une application graphique de type scanner de ports avec Tkinter !

Conclusion

Dans ce tutoriel, on a vu comment créer un scanner de ports à l’aide de quelques modules Python. On a réalisé une belle petite interface graphique et produit une version exécutable de notre programme.