mitmproxy for fun and profit: Interception et analyse de flux applicatifs
Une connaissance fine des protocoles utilisés par les applications s’avère être un prérequis nécessaire pour étudier la sécurité des applications. Lors de ces dernières, nous avons été amenés à intercepter différents types de flux réseau sur différentes plateformes, notamment Linux, Android ou bien iOS. Cet article a pour objectif de présenter l’outil mitmproxy et son utilisation, ainsi que les différentes techniques mises en œuvre afin d’intercepter efficacement ces communications, en tenant compte des spécificités propres à chaque environnement.
Vous souhaitez améliorer vos compétences ? Découvrez nos sessions de formation ! En savoir plus
Présentation de l’outil
mitmproxy est un outil open-source, principalement développé en Python, permettant d’analyser, de modifier et de rejouer des communications réseau à différentes couches protocolaires (principalement HTTP et HTTPS), mais peut également traiter directement en couche transport avec les protocoles TCP ou bien UDP. Il appartient à la catégorie des outils de type MITM (Man-In-The-Middle) en s’intercalant entre un client et un serveur. L’outil peut être utilisé en mode explicite, où le proxy est configuré directement sur l’appareil, ou en mode transparent, dans lequel l’appareil n’a pas conscience de la présence du proxy.
En mode explicite, le proxy est configuré directement sur l’équipement cible (poste de travail, smartphone, émulateur, etc.) via les réglages spécifiques au type de connexion. L’application ou le système d’exploitation est alors conscient de l’utilisation d’un proxy, et l’ensemble du trafic concerné est volontairement redirigé vers mitmproxy. Ce mode est généralement le plus simple à mettre en œuvre car il ne requiert que peu de configuration sur l’appareil. Dans ce mode, les applications ne respectant pas la configuration du proxy sur l’appareil ne verront pas leur trafic transiter par mitmproxy, sauf si des règles de firewall forcent la redirection de ces paquets.
À l’inverse, le mode transparent ne nécessite aucune configuration préalable côté client. Le trafic est alors redirigé vers l’instance mitmproxy à l’aide de mécanismes réseau tels que des règles de routage ou de redirection (en utilisant par exemple les outils iptables ou nftables). L’équipement cible n’a donc aucune connaissance de la présence du proxy. Ce mode est particulièrement utile lorsque la configuration d’un proxy explicite n’est pas possible ou lorsque l’on souhaite observer un comportement applicatif sans modification de l’environnement client. En mode transparent, mitmproxy s’appuie sur l’extension SNI du ClientHello TLS pour déterminer dynamiquement le domaine pour lequel forger le certificat. Il a cependant le désavantage de laisser les applications utiliser les protocoles qu’elles souhaitent pour leurs flux sortants, ce qui amène à la présence de protocoles comme QUIC (basé sur UDP), protocole largement utilisé sur les appareils mobiles.
Dans ces deux modes, et sauf cas particulier (par exemple une absence de validation TLS côté client), l’installation du certificat racine généré par mitmproxy est nécessaire sur l’appareil afin d’accéder au contenu des échanges chiffrés. Il s’agit là du point crucial dans l’étude des échanges réseau, et ce peu importe le système d’exploitation. Cependant, certaines applications mettent en place des mécanismes de Certificate Pinning, limitant strictement les certificats de confiance acceptés pour un ou plusieurs domaines. Dans ce cas, l’interception devient impossible sans la mise en œuvre de contre-mesures spécifiques, visant à contourner ou neutraliser ces protections…
D’un point de vue réseau, l’interception peut être réalisée à différents niveaux:
- Au niveau de la couche transport, via l’interception de flux TCP ou UDP.
- Au niveau applicatif, en agissant comme un proxy HTTP explicite ou bien transparent.
- Au niveau TLS, permettant le déchiffrement des communications chiffrées, notamment celles reposant sur HTTPS.
La configuration et l’utilisation de mitmproxy se veulent relativement simples. L’outil peut être déployé directement sur l’hôte ou exécuté au sein d’une image OCI fournie par les développeurs.
Trois outils principaux sont fournis lors de l’installation du paquet:
mitmproxy, permettant de lancer l’interception de façon interactive directement en ligne de commande avec une interface directement intégrée au sein du terminalmitmweb, permettant lui aussi de lancer l’interception mais offrant une visualisation depuis une IHM webmitmdump, la variante non interactive permettant l’injection de scripts et le dump des différents flux réseau dans un format propre à l’outil.
Parmi ses fonctionnalités clés, mitmproxy propose aussi une API de scripting permettant le développement de modules personnalisés pour la manipulation fine des requêtes et réponses à différents niveaux protocolaires. Cette capacité constitue l’un des principaux atouts de l’outil et sera exploitée à plusieurs reprises tout au long de cet article.
Setup réseau pour interception
Cette partie se concentre sur la mise en place d’un laboratoire sur Linux en utilisant des fonctionnalités présentes dans le noyau et ne s’applique donc pas directement à d’autres systèmes d’exploitation comme Windows ou macOS.
Notre environnement réseau se base sur l’utilisation de namespace réseau, un mécanisme disponible dans le kernel Linux depuis la version 2.6.24 (janvier 2008). La mise en place de ces namespaces rend possible une isolation de la stack réseau (tables de routage, firewall, interfaces…) entre les processus lancés au sein du namespace et ceux lancés en dehors. Leur création est très simple en utilisant l’utilitaireip et nécessite l’obtention des droits root.
En utilisant l'utilitaire ip...
ip netns add <MITM_NAMESPACE_NAME>
Il est par exemple possible d’avoir une version différente du ficher resolv.conf au sein du namespace en créant ce fichier dans le dossier /etc/netns/<MITM_NAMESPACE_NAME>/.
Notre namespace nouvellement instancié, nous allons pouvoir lui monter l’ensemble des interfaces nécessaires à son bon fonctionnement, à savoir l’interface de loopback (lo) ainsi que l’interface wifi (nommée wlan0 dans notre exemple). Cette dernière nous permettra de connecter des appareils mobiles sur le même réseau que notre proxy. Il est tout de même à noter que selon la configuration du système sur lequel sont lancées les commandes, il peut être nécessaire de modifier certains paramètres pour éviter que des services comme NetworkManager ou wpa_supplicant ne reprennent le contrôle de l’interface.
WIFI_IFACE=wlan0
ip netns exec <MITM_NAMESPACE_NAME> ip link set lo up
ip link set $WIFI_IFACE down
ip link set $WIFI_IFACE netns <MITM_NAMESPACE_NAME>
ip netns exec <MITM_NAMESPACE_NAME> ip link set $WIFI_IFACE up
La commande ip netns exec <MITM_NAMESPACE_NAME> permet de lancer des commandes à l’intérieur du namespace réseau et non pas sur l’hôte directement.
À partir de ce moment, notre interface Wifi n’est plus accessible depuis le namespace principal et est disponible uniquement dans notre nouveau namespace.
Nous allons maintenant pouvoir créer notre point d’accès Wifi en utilisant le script lnxrouter, permettant la création du point d’accès, la gestion des règles de firewall et le routage vers une interface de sortie donnée.
Le lancement de l’utilitaire est assez simple:
AP_NAME=mitm_lab
AP_PASS=VerySecurePasswd
OUTPUT_IFACE=tun0
ip netns exec <MITM_NAMESPACE_NAME> lnxrouter \
--ap $WIFI_IFACE $AP_NAME \
-p $AP_PASS \
-o $OUTPUT_IFACE \
--dhcp-range=10.10.0.10,10.10.0.100,255.255.255.0,24h
Du point de vue de l’interface de sortie, la seule contrainte est que cette dernière doit exister au sein du namespace réseau. Il est possible d’imaginer de nombreux scénarios différents, ceci dépendant de votre cas d’usage:
- Sortie sur un VPS maîtrisé via un tunnel VPN (
wireguard,openvpn…) - Sortie sur un VPS maîtrisé via un tunnel SSH (via l’option
-w) - Sortie depuis l’interface ethernet de l’hôte
- …
La mise en place du serveur DHCP est aussi gérée par lnxrouter, et certains paramètres sont directement configurables depuis les options proposées.
Il est aussi nécessaire de rajouter des règles au niveau du firewall dans le cas d’un lancement du mitmproxy en mode transparent. Ces règles permettent de rediriger l’ensemble des flux en HTTP et HTTPS via du DNAT vers notre instance de mitmproxy.
ip netns exec <MITM_NAMESPACE_NAME> nft add table nat
ip netns exec <MITM_NAMESPACE_NAME> nft add chain nat prerouting { type nat hook prerouting priority -100 \; }
ip netns exec <MITM_NAMESPACE_NAME> nft add rule nat prerouting iifname <WIFI_IFACE> ip daddr != <MITMPROXY_IP_ADDR> tcp dport 80 redirect to <MITMPROXY_TRANSPARENT_PORT>
ip netns exec <MITM_NAMESPACE_NAME> nft add rule nat prerouting iifname <WIFI_IFACE> ip daddr != <MITMPROXY_IP_ADDR> tcp dport 443 redirect to <MITMPROXY_TRANSPARENT_PORT>
ip netns exec <MITM_NAMESPACE_NAME> nft add rule nat prerouting iifname <WIFI_IFACE> udp dport 53 redirect to <MITMPROXY_TRANSPARENT_DNS_PORT>
Concernant le lancement de l’outil mitmproxy:
mitmweb -s scripts/inspection.py \
--web-open-browser \
--mode [regular|transparent] \
--set web_host=$MITMWEB_HOST \
--set web_port=$MITMWEB_PORT \
--ignore-hosts="$IGNORE_HOSTS_REGEXP"
Après une présentation de l’outil, de ses fonctionnalités ainsi que de l’environnement réseau sur lequel nous allons nous appuyer pour la suite de l’étude, nous allons maintenant passer à des exemples d’interception sur les différentes plateformes citées en introduction.
Altération de flux applicatif sur Linux
Cette partie de l’étude va se focaliser sur la visualisation et la manipulation des flux réseaux réalisés par l’outil git dans le cadre du clone d’un projet.
Notre namespace réseau nouvellement créé, nous allons pouvoir y lancer l’outil mitmweb à l’intérieur (en écouter sur localhost:8080 pour les besoins de la démonstration).
Nous allons aussi pouvoir y lancer un git clone d’un projet arbitraire, nous choisirons pour notre exemple le projet nmap. Afin de forcer l’utilisation du proxy, git respecte les variables d’environnement HTTP_PROXY et HTTPS_PROXY que nous allons pouvoir renseigner à la valeur http://127.0.0.1:8080. La CA de notre mitmproxy n’étant pas présente dans la liste certificats racines validés sur notre système, il est nécessaire de spécifier à git le chemin vers notre certificat racine. Ceci est réalisable en utilisant la variable d’environnement GIT_SSL_CAINFO, mais nous aurions aussi pu désactiver totalement la vérification des certificats en renseignant la variable GIT_SSL_NO_VERIFY=true. Cette attaque suppose que l’utilisateur ne réalise pas de vérification des signatures de commits ou de tags, celles-ci n’étant pas automatiquement validées par défaut.
Le lancement du clone de notre projet git peut alors se faire directement au sein du namespace réseau afin de profiter de la route par défaut configurée, du potentiel fichier resolv.conf spécifique au namespace ainsi que des règles de pare-feu appropriées pour nos traitements.
HTTP_PROXY=http://127.0.0.1:8080 \
HTTPS_PROXY=http://127.0.0.1:8080 \
GIT_SSL_CAINFO=<PATH_TO_MITMPROXY_CA_CERT> \
git clone https://github.com/nmap/nmap
Nous voyons typiquement 3 requêtes principales sur notre mitmproxy:
- La première permet au client de récupérer des informations sur le serveur et sur le dépôt
- La seconde permet d’énumérer la liste des références présentes sur le serveur, comme les branches ou les tags
- La troisième permet de transférer le code source, sous forme d’objets Git
D’un point de vue sécurité, nous allons nous attarder sur l’interception des 2 dernières requêtes afin de faire télécharger à git un tout autre dépôt de code, par exemple Magisk. Il aurait été tout à fait possible de renvoyer une version modifiée du projet nmap, comme par exemple une version incluant une backdoor. L’attaque ne modifie pas le protocole Git lui-même mais redirige simplement les requêtes HTTP vers un autre dépôt, laissant le serveur distant gérer normalement la négociation du protocole Git.
from mitmproxy import ctx, http
SRC_REPO_PATH = "/nmap/nmap"
DST_REPO_PATH = "/topjohnwu/Magisk"
def request(flow: http.HTTPFlow):
req = flow.request
# We only want to alter github domains
if req.host != "github.com":
return
# info/refs?service=git-upload-pack
if req.path.startswith(f"{SRC_REPO_PATH}/info/refs"):
req.path = req.path.replace(SRC_REPO_PATH, DST_REPO_PATH)
ctx.log.info(f"[info/refs] {SRC_REPO_PATH} → {DST_REPO_PATH}")
# git-upload-pack (ls-refs, fetch)
elif req.path.startswith(f"{SRC_REPO_PATH}/git-upload-pack"):
req.path = req.path.replace(SRC_REPO_PATH, DST_REPO_PATH)
ctx.log.info(f"[git-upload-pack] {SRC_REPO_PATH} → {DST_REPO_PATH}")
En regardant de plus près les retours de commandes git dans le répertoire de code:
$ git remote -v
# nothing suspicious...
origin https://github.com/nmap/nmap (fetch)
origin https://github.com/nmap/nmap (push)
$ git log HEAD^1..HEAD
# suspicious output showing latest magisk commit...
Cette interception nous montre assez clairement la facilité et les possibilités offertes par mitmproxy du point de vue de l’altération de flux réseau. Dans notre exemple l’environnement est totalement contrôlé mais on pourrait imaginer appliquer le même type d’attaque sur un poste victime ayant une configuration de git plutôt “permissive”.
Interception et modification de réponses sous un appareil Android
Le problème de l’acceptation des certificats racines reste le même pour les appareils mobiles, avec la difficulté supplémentaire que ces derniers ne sont pas facilement modifiables. Sous Android, la gestion des certificats repose sur deux notions: les certificats utilisateurs et les certificats système.
Depuis Android 7 (Nougat), les applications ne font plus confiance aux certificats utilisateur par défaut, d’où la nécessité de les installer au niveau système pour intercepter certaines applications[1].
Les certificats système sont disponibles dans le dossier /system/etc/security/cacerts, et sont accessibles uniquement en read-only. Ils sont aussi visibles depuis les paramètres Système, sous Security > More security & privacy > Encryption and Credentials > Trusted Credentials.
Il est donc nécessaire de disposer d’un appareil rooté (avec Magisk par exemple) afin de pouvoir altérer le contenu de cette partition en lecture / écriture le plus tôt possible dans la chaîne de démarrage de l’appareil. Magisk permet de facilement installer des “modules”, et l’un d’entre eux nommé Cert-Fixer permet de copier l’ensemble des certificats utilisateurs dans le répertoire des certificats système. Cette méthode nous permet donc de voir notre certificat mitmproxy comme un certificat avec le plus haut niveau de confiance sur notre appareil.
| Liste des certificats utilisateurs | Liste des certificats système |
|---|---|
|
|
Nous allons maintenant utiliser mitmproxy afin de visualiser et modifier une requête réalisée vers un serveur de Google. Notre choix s’est porté sur l’interception des requêtes vers le domaine geomobileservices-pa.googleapis.com, domaine régulièrement contacté en gRPC par le package com.google.android.gms afin de résoudre une localisation sous forme de (latitude, longitude) en adresse “textuelle”.
La requête est réalisée sur l’URL complète https://geomobileservices-pa.googleapis.com/google.internal.maps.geomob…
Comme de nombreuses requêtes réalisées sur plateformes mobiles le format des données échangées s'appuie sur protobuf, un format de sérialisation développé par Google.
Nous allons donc développer un script mitmproxy afin de parser le contenu de cette requête en s'appuyant sur le fichier de description associé, puis en modifier le contenu des champs latitude et longitude afin de spoofer notre position réelle auprès des serveurs Google.
Tout d’abord et afin de pouvoir facilement reproduire cette requête, le code suivant sera éxecuté sur le téléphone en tant que system_server:
public class GeocodeCallback extends IGeocodeCallback.Stub {
private static final String TAG = "GeocodeCallback";
public GeocodeCallback() {
}
@Override
public void onError(String arg0) throws RemoteException {
Log.e(TAG, "got error: " + arg0);
}
@Override
public void onResults(List<Address> arg0) throws RemoteException {
Logger.info("got reverse location result:");
for (Address addr : arg0) {
Log.i(TAG, "\tcountry=" + addr.getCountryName());
Log.i(TAG, "\tcountryCode=" + addr.getCountryCode());
Log.i(TAG, "\taddress=" + addr.getAddressLine(0));
}
}
}
public class Main {
public static void main(String[] args) {
try {
IBinder locationService = ServiceManager
.getService(Context.LOCATION_SERVICE);
ILocationManager locationManager = ILocationManager.Stub.asInterface(locationService);
/* Eiffel Tower coordinates */
Pair<Double, Double> coordinates = Pair.create(48.636093, -1.511457);
ReverseGeocodeRequest request = new ReverseGeocodeRequest.Builder(
coordinates.first,
coordinates.second,
1, /* max results in response */
Locale.US,
1000, /* system_server UID */
"android" /* system_server package name */
).build();
} catch (Exception e) {
Logger.error("got exception: " + e);
}
}
}
Le lancement de ce POC dans le bon contexte nous permet ainsi de visualiser le flux directement dans l’interface de mitmproxy, et nous permet ainsi de deviner le format de requête attendu. Le format “précis” de ces données peut aussi se retrouver en analysant le code source d’Android, et en particulier la classe Java ReverseGeocodeRequest.
La requête se présente comme suit:
Et la réponse:
Une fois ces informations interceptées, nous pouvons en déduire le format de message protobuf suivant:
syntax = "proto3";
message ReverseGeocodeRequest {
Location location = 1;
string locale = 3;
int64 max_results = 6;
string package_name = 7;
message Location {
double latitude = 1;
double longitude = 2;
}
}
Ce schéma peux ensuite être “compilé” vers du Python en utilisant le compilateur protobuf protoc. Ceci permet d’en facilier l’intégration dans notre script mitmproxy:
protoc --proto_path=. --python_out=. reverse_geocode.proto
Et ensuite développer notre script mitmproxy afin de modifier à la volée les coordonnées remontées par notre appareil:
GRPC_FRAMING_LEN = 5
def should_intercept(request: http.Request) -> bool:
content_type = request.headers.get("content-type", "")
host = request.host
path = request.path
return (
content_type.lower().startswith("application/grpc")
and host == "geomobileservices-pa.googleapis.com"
and path.endswith("/ReverseGeocode")
)
def request(flow: http.HTTPFlow):
if not should_intercept(flow.request):
# likely not the content we want to intercept
return
uncompressed_content = flow.request.content
if not uncompressed_content:
ctx.log.error("error while getting uncompressed content")
return
# gRPC framing:
# 1 byte -> compression
# 4 bytes -> message length (big endian)
# N bytes -> message
if len(uncompressed_content) < GRPC_FRAMING_LEN:
return
# assuming here that data isn't sent compressed
message_length = int.from_bytes(
uncompressed_content[1:GRPC_FRAMING_LEN], byteorder="big"
)
message = uncompressed_content[GRPC_FRAMING_LEN : GRPC_FRAMING_LEN + message_length]
geocode_request = ReverseGeocodeRequest()
geocode_request.ParseFromString(message)
# Alter request content
geocode_request.location.latitude = 48.8584
geocode_request.location.longitude = 2.2945
# .........
# Serialize back data (gRPC-framed)
new_payload = geocode_request.SerializeToString()
new_payload_data = bytes([0]) + len(new_payload).to_bytes(4, byteorder="big") + new_payload
# .........
flow.request.content = new_payload_data
ctx.log.info("Successfully replaced payload")
Et au final les requêtes de localisation “légitimes” réalisées par le téléphone (et en particulier celles réalisées par le package com.android.phone) sont donc maintenant localisées depuis la Tour Eiffel et non plus depuis nos locaux parisiens 🙂.
Requête interceptée:
Et la réponse reçue depuis les serveurs de Google:
Il ne s’agit bien évidemment pas du seul moyen utilisé par Google pour récupérer la localisation de ses utilisateurs et cette étude devrait être menée sur bien plus de couples (domaines / endpoints) afin d’être exhaustive. L’objectif de cette preuve de concept étant de démontrer la facilité avec laquelle il est possible de modifier des données avec une instance de mitmproxy placée en interception, même dans le cadre de protocoles comme gRPC.
Analyse de l’application Mumble sur iOS
Cette dernière partie de l’étude va se concentrer sur l’analyse et l’interception réseau sous iOS, et en particulier sur le dumping passif des messages textuels échangés vers un serveur par l’application Mumble.
Mise en place du serveur
Le code du serveur est open-source et est disponible dans la plupart des distributions de paquets Linux. Pour ceux ne souhaitant pas installer le paquet il est aussi possible de le mettre en place via une image Docker (solution que nous allons utiliser dans le cadre de cette étude). Le script bash suivant permet de lancer le containeur:
#! /bin/bash
set -e
MUMBLE_DATA_FOLDER=$(pwd)/data
mkdir ${MUMBLE_DATA_FOLDER} || true
podman run --rm \
--name mumble-server \
--userns=keep-id \
-u "$(id -u):$(id -g)" \
--publish 64738:64738/tcp \
--publish 64738:64738/udp \
--volume ${MUMBLE_DATA_FOLDER}:/data \
--restart on-failure \
-e MUMBLE_CHOWN_DATA=false \
ghcr.io/mumble-voip/mumble-server:latest
Aspects protocolaires et encapsulation des données
L’application communique avec le serveur en TCP, en surchiffrant les données applicatives en utilisant le protocole TLS. La vérification forte via certificate pinning n’est pas obligatoire comme la liste des serveurs n’est pas connue à l’avance et fixe (ce qui est normal comme les serveurs peuvent être auto-hébergés). Les messages ne sont pas non plus surchiffrés à l’intérieur des échanges TLS, ce qui permet de récupérer les données en clair en cas de compromission du canal d’échange (via l’acceptation d’un certificat TLS auto-signé ou non valide pour le domaine contacté par exemple). Le protocole applicatif utilisé est du protobuf, et l’ensemble des schémas sont disponibles sur le github du projet. Dans notre cas, le schéma Mumble.proto va nous intérésser, et en particulier le type de message TextMessage contenant le message à proprement parler, ainsi que certaines métadonnées:
message TextMessage {
optional uint32 actor = 1;
repeated uint32 session = 2;
repeated uint32 channel_id = 3;
repeated uint32 tree_id = 4;
required string message = 5;
}
Ces éléments nous permettent ainsi de mettre en place notre instance de mitmproxy en mode reverse:tls. Notre instance écoute sur le port “standard” de mumble (à savoir 64738), et une fois les données interceptées va les rediriger vers notre serveur mumble (via TLS) écoutant sur le port 64000.
MUMBLE_HOST=192.168.12.1
MUMBLE_PORT=64000
MITMPROXY_PORT=64738
mitmweb \
--ssl-insecure \
--mode reverse:tls://$(MUMBLE_HOST):$(MUMBLE_PORT) \
--listen-port $(MITMPROXY_PORT) \
--set web_port=9008 \
--set web_host=192.168.20.2 \
-s intercept_mumble_messages.py
Le script d’interception intercept_mumble_messages.py se base sur la compilation préalable du fichier protobuf:
protoc --proto_path=. --python_out=. Mumble.proto
Et se veut au final assez simple. Un buffering des données TCP est mis en place afin de gérer les messages arrivant sur plusieurs paquets TCP. Pour la preuve de concept les données sont gardées en mémoire par session TCP et tout au long de la durée de vie du programme.
from mitmproxy import tcp
import struct
import Mumble_pb2
# Interesting message types
MESSAGE_TYPES = {
9: Mumble_pb2.UserState,
11: Mumble_pb2.TextMessage,
}
class MumbleMessageLogger:
def __init__(self):
self.buffers = {}
def tcp_start(self, flow: tcp.TCPFlow):
self.buffers[flow.id] = b""
def tcp_message(self, flow: tcp.TCPFlow):
data = flow.messages[-1].content
buffer = self.buffers.get(flow.id, b"") + data
offset = 0
while True:
if len(buffer[offset:]) < 6:
# not enough data to read header
break
# 2 bytes type, 4 bytes length
msg_type = struct.unpack(">H", buffer[offset:offset+2])[0]
length = struct.unpack(">I", buffer[offset+2:offset+6])[0]
if len(buffer[offset+6:]) < length:
# incomplete payload, waiting for another message
break
payload = buffer[offset+6:offset+6+length]
if msg_type in MESSAGE_TYPES:
try:
msg = MESSAGE_TYPES[msg_type]()
msg.ParseFromString(payload)
if isinstance(msg, Mumble_pb2.TextMessage):
print(f"Actor {msg.actor} sent {msg.message}")
elif isinstance(msg, UserState):
print(f"[USER JOIN] -> {msg.name or "unknown"}")
except Exception as e:
print("Protobuf parse error:", e)
offset += 6 + length
# keep remaining bytes for next segment
self.buffers[flow.id] = buffer[offset:]
def tcp_end(self, flow: tcp.TCPFlow):
self.buffers.pop(flow.id, None)
addons = [
MumbleMessageLogger()
]
Nous retrouvons ensuite les messages affichés dans notre terminal:
[USER JOIN] -> phone2
[MESSAGE] -> Welcome to the server @phone2
[MESSAGE] -> hello world :)
Ce POC peut être amélioré pour y rajouter l’affichage du nom d’utilisateur ayant envoyé le message, les utilisateurs ayant quitté le serveur… De nombreux autres types de messages protobuf peuvent être envoyés par les clients et le serveur et sont disponibles dans le fichier Mumble.proto.
Conclusion
Cet article a montré la facilité avec laquelle mitmproxy permet d’intercepter et d’analyser des flux applicatifs sur Linux, Android et iOS. Nous avons vu que la maîtrise des couches réseau est indispensable pour comprendre et manipuler les communications, que ce soit pour explorer des protocoles ou en tester la sécurité. Malgré le chiffrement, l’interception reste possible avec les bons certificats et la connaissance des protocoles, mais des protections comme le certificate pinning ou le chiffrement applicatif peuvent en limiter cette capacité.
[1] Android Privacy and Security: https://developer.android.com/privacy-and-security/security-config#base…