Garde-fous intégrés à ce lab :
- Périmètre : uniquement tes propres appareils (téléphone perso, PC, Mac), avec ton consentement explicite à chaque demande d’accès.
- Aucune cible tierce : on ne teste jamais sur l’appareil de quelqu’un d’autre, même avec autorisation orale - utilise tes propres machines.
- Aucune charge utilisable : les charges embarquées dans les cobayes sont des
marqueurs de démonstration (
LAB_ONLY,alert('demo')). - Cobayes confinés : les fichiers
polyglot.php.gif,exif.jpg,appended.jpg,demo.svgrestent dans~/Bureau/sensor-trace-lab/image-piegee/et sont supprimés en fin d’exercice. - Pas de persistance : aucune des pages ou commandes ne modifie de configuration système sans que tu ne l’aies explicitement fait (toutes les actions sont documentées, réversibles, et rappelées en fin de section).
Théorie - les pastilles, les journaux, et leurs limites
Chaque OS moderne signale l’accès aux capteurs par un indicateur visuel en temps réel (pastille, icône, point coloré) et conserve un journal d’activité consultable après coup. Ces deux sources ne racontent pas la même chose : la pastille dit « en ce moment », le journal dit « récemment, combien de fois, et par qui ». Un lab sérieux corrèle les deux.
Android 12+ - pastille verte et Privacy Dashboard
Un point vert (ou une icône) apparaît dans la barre d’état dès qu’une application utilise le micro ou la caméra. Un point bleu signale parfois l’accès à la localisation selon la version. Ces indicateurs sont impossibles à masquer côté application.
Journal : Paramètres > Sécurité et confidentialité > Tableau de bord de
confidentialité. Il liste, par heure et par application, les accès au micro, à la
caméra et à la localisation.
iOS 14+ - point orange, point vert
Point orange = micro. Point vert = caméra (ou caméra + micro). Le Centre de contrôle affiche la dernière application ayant accédé. Depuis iOS 15, le Rapport de confidentialité de l’app conserve l’historique détaillé (heure, app, type d’accès).
Journal : Réglages > Confidentialité et sécurité > Rapport de
confidentialité de l’app.
Windows 10/11 - icône de notification
Une icône de micro ou une flèche de localisation s’affiche dans la zone de notification pendant l’accès. Le journal d’activité (Paramètres > Confidentialité & sécurité > Autorisations des applications) indique, par application, l’historique récent.
Registre : HKCU\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\<capteur>
liste les applications ayant demandé/obtenu l’accès.
macOS 12+ - points du Centre de contrôle
À droite de l’icône du Centre de contrôle, des points colorés apparaissent : orange = micro, vert = caméra, violet = enregistrement audio système, flèche = localisation. Centre de contrôle > partie supérieure liste les apps actives.
Journal unifié : log show --predicate 'subsystem == "com.apple.TCC"'
- le sous-système TCC (Transparency, Consent, Control) garde les décisions d’autorisation
et parfois les accès effectifs.
Le point commun : aucune pastille ne dit ce qui est fait avec le flux, seulement qu’il y a flux. Un accès légitime (visio) et un accès suspect (enregistrement silencieux) allument la même pastille. C’est pourquoi on corrèle systématiquement avec :
- le journal d’activité natif de l’OS (heure, app, durée),
- la page web en cours (un site peut déclencher
getUserMediavia le navigateur, sans permission « app »), - les outils tiers (Grapefruit, OVERSIGHT, scripts ADB/PowerShell),
- les traces fichiers quand une page charge des ressources (c’est là qu’intervient l’image piégée).
Architecture du lab
pastille + Privacy Dashboard"] I["iOS 14+
point orange/vert + Rapport"] W["Windows 10/11
icone + Recent activity"] M["macOS 12+
points + log show TCC"] end subgraph PAGE["Page HTML locale (page-test.html)"] G["getUserMedia()
demande micro / camera / geo"] J["<img src=...>
charge les cobayes"] end subgraph COB["Cobayes LAB_ONLY (image-piegee/)"] P1["polyglot.php.gif"] P2["exif.jpg"] P3["appended.jpg"] P4["demo.svg"] end subgraph OUT["Outils def."] T1["file / exiftool / xxd"] T2["strings / binwalk / foremost"] T3["scan_images.py"] T4["YARA (image_polyglot.yar)"] end subgraph JOUR["Journaux a correler"] L1["pastille temps reel"] L2["journal activite OS"] L3["rapport scan_images.py"] L4["YARA + Suricata (optionnel)"] end DEV -- "consentement explicite" --> PAGE PAGE -- "charge" --> COB COB -- "audit" --> OUT OUT -- "produit" --> JOUR L1 -. "correlation" .- L2 L2 -. "correlation" .- L3 L3 -. "correlation" .- L4
APPAREILS (consentants)
Android / iOS / Windows / macOS
| consentement explicite
v
page-test.html (getUserMedia + img coby)
| charge
v
cobayes LAB_ONLY : polyglot.php.gif | exif.jpg | appended.jpg | demo.svg
| audit
v
file | exiftool | xxd | strings | binwalk | foremost | scan_images.py | YARA
| produit
v
JOURNAUX A CORRELER : pastille + journal OS + rapport scan + alertes YARA
\______ 4 sources = 1 evenement ______/
Si le diagramme ne s’affiche pas (CDN bloqué), le schéma ASCII de secours apparaît automatiquement.
Prérequis
- Ta machine Kali (ou Debian/Ubuntu) avec Terminal, et - selon l’appareil testé - ton téléphone Android, ton iPhone, ton PC Windows, ton Mac.
- Outils en ligne de commande côté Linux/Kali :
exiftool,binwalk,foremost,strings,xxd,file,identify(ImageMagick),yara,python3. - Côté Android : adb (Android Platform Tools), mode développeur et debug USB activés uniquement le temps de l’exercice.
- Côté Windows : PowerShell (natif). Côté macOS : Terminal (natif).
- Tout se passe en local. Ne lance rien sur l’appareil de quelqu’un d’autre. Aucun fichier cobaye ne sort du dossier du lab.
Installation des outils sur Kali :
sudo apt update
sudo apt install -y exiftool binwalk foremost imagemagick yara python3 android-tools-adb
Étape 1 Créer l’arborescence du lab
Tout tient dans un seul dossier de ton Bureau. Copie et exécute :
# Cree l'arborescence du lab sur ton Bureau
mkdir -p ~/Bureau/sensor-trace-lab/{journaux,image-piegee,scripts,yara,page}
cd ~/Bureau/sensor-trace-lab
echo "Lab SensorTrace pret dans $(pwd)"
ls -la
Étape 2 Android - Privacy Dashboard + ADB
Sur ton téléphone Android (12+), vérifie d’abord la pastille et le Privacy Dashboard. Puis, si tu veux automatiser, active le debug USB et branche le téléphone à ton Kali.
# 1) Verifier que le telephone est bien vu
adb devices
# 2) Evenements recents par capteur (equivalent Privacy Dashboard)
adb shell cmd privacy get-recent-events --sensor-type camera
adb shell cmd privacy get-recent-events --sensor-type microphone
adb shell cmd privacy get-recent-events --sensor-type location
# 3) Permissions accordees (liste par paquet)
adb shell dumpsys package | grep -E "android.permission.(CAMERA|RECORD_AUDIO|ACCESS_FINE_LOCATION)"
# 4) Voir qui a la permission MICROPHONE active
adb shell pm list packages -p | head -20
# 5) Desactiver le debug USB a la fin (sur le telephone : Options de developpeur)
Le piège classique : un site dans Chrome peut utiliser ton micro/caméra sans que Chrome ait la permission “app”. La pastille s’allume - mais l’enquête doit descendre au niveau du site : dans Chrome, icône 🔒 dans la barre d’adresse > Autorisations. C’est un cas d’école pour la corrélation.
Étape 3 iOS - Rapport de confidentialité
Sur ton iPhone (iOS 15+), fais le tour des sources : pastille, Centre de contrôle, rapport de confidentialité, et permissions par app.
# 1) Pastille en temps reel : barre d'etat
# point orange = micro ; point vert = camera (ou les deux)
# 2) Centre de controle = derniere app ayant accede
Glisser du coin superieur droit vers le bas
-> la derniere app ayant utilise micro/camera s'affiche en haut
# 3) Rapport de confidentialite (historique, le plus utile)
Reglages > Confidentialite et securite > Rapport de confidentialite de l'app
-> liste des acces par app, par heure, par type (micro / camera / geo)
# 4) Permissions par app
Reglages > Confidentialite et securite > Microphone
Reglages > Confidentialite et securite > Appareil photo
Reglages > Confidentialite et securite > Service de localisation
# 5) Cas suspect : app qui demande le micro mais n'en a pas besoin
# -> verifier dans le rapport si elle a accede en arriere-plan
Limite documentée : certains spywares avancés ont su masquer la pastille (recherche Predator). D’où l’importance de ne pas se fier uniquement à la pastille : le Rapport de confidentialité reste la source la plus fiable côté iOS non jailbreaké.
Étape 4 Windows 10/11 - Recent activity + PowerShell
Sur ton PC Windows, vérifie l’icône de notification et le journal d’activité. Puis, en PowerShell, liste les applications ayant accédé aux capteurs.
# 1) Journal d'activite "Recent activity" (GUI)
# Parametres > Confidentialite et securite > Autorisations des applications
# > Microphone / Appareil photo / Localisation
# 2) Liste des applications ayant accede au micro
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\*" |
Select-Object PSChildName, LastUsedTimeStart, LastUsedTimeStop
# 3) Liste des applications ayant accede a la camera
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\webcam\*" |
Select-Object PSChildName, LastUsedTimeStart, LastUsedTimeStop
# 4) Liste des applications ayant accede a la localisation
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\location\*" |
Select-Object PSChildName, LastUsedTimeStart, LastUsedTimeStop
# 5) Cas suspect : application avec LastUsedTimeStop a 0
# -> acces encore en cours au moment du controle
Le piège classique : une webcam externe n’a pas toujours de LED physique. L’icône Windows peut ne pas s’afficher pour un accès très bref. On corrèle donc icône + journal d’activité, jamais l’un sans l’autre.
Étape 5 macOS 12+ - Centre de contrôle + log show
Sur ton Mac, vérifie les points colorés et le Centre de contrôle. Puis, en Terminal, audite les décisions TCC (Transparency, Consent, Control).
# 1) Points colores dans la barre des menus
# orange = micro ; vert = camera ; violet = audio systeme ; fleche = geo
# 2) Centre de controle : apps actives en haut
# Clic sur l'icone dans la barre des menus
# 3) Reglages systeme > Confidentialite et securite
# > Microphone / Camera / Localisation
# 4) Audit avance : decisions TCC sur la derniere heure
log show --predicate 'subsystem == "com.apple.TCC"' --last 1h
# 5) Filtrer les acces camera
log show --predicate 'subsystem == "com.apple.TCC" AND eventMessage CONTAINS "camera"' --last 1h
# 6) Filtrer les acces micro
log show --predicate 'subsystem == "com.apple.TCC" AND eventMessage CONTAINS "microphone"' --last 1h
# 7) Filtrer les acces localisation
log show --predicate 'subsystem == "com.apple.TCC" AND eventMessage CONTAINS "location"' --last 1h
Le piège classique : depuis macOS Sonoma 14.4, les indicateurs peuvent
être masqués sur un écran externe en plein écran. Si tu utilises un moniteur
externe, la pastille peut ne pas être visible : log show reste ton filet de
sécurité.
Étape 6 Page de test locale (getUserMedia + cobayes)
Cette page te sert de banc d’essai : elle demande l’accès aux capteurs et charge
les cobayes. Ouvre-la depuis file:// ou un petit serveur local, jamais
depuis un site public. Elle te permet de provoquer un accès volontaire et de
vérifier que la pastille, le journal OS et scan_images.py racontent la
même histoire.
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>SensorTrace - page de test locale</title>
<style>
body{font-family:system-ui,sans-serif;background:#0c0a0f;color:#ece7f3;padding:2rem;max-width:900px;margin:auto}
button{background:#c21f6b;color:#fff;border:0;padding:.6rem 1rem;border-radius:8px;font-weight:700;cursor:pointer;margin:.2rem}
#status{background:#1b1724;border:1px solid #322a3f;border-radius:8px;padding:.8rem;margin:1rem 0;font-family:monospace;white-space:pre-wrap}
img.coby{border:2px dashed #ff8a3d;max-width:160px;margin:.3rem;background:#15121b}
.banner{background:linear-gradient(90deg,rgba(255,85,85,.18),rgba(255,85,85,.04));border:1px solid #ff5555;border-left:6px solid #ff5555;border-radius:10px;padding:.8rem 1rem;margin-bottom:1rem}
.banner b{color:#ff9a9a}
</style>
</head>
<body>
<div class="banner">
<b>LAB_ONLY</b> - page de test. Toutes les demandes d'acces sont
volontaires. Les images ci-dessous sont des cobayes inoffensifs
(charges de demo uniquement). Ne pas heberger publiquement.
</div>
<h1>SensorTrace - page de test</h1>
<p>Chaque bouton declenche une demande <code>getUserMedia</code>.
Regarde la pastille de l'OS pendant l'acces, puis ouvre le journal
d'activite pour comparer.</p>
<p>
<button id="cam">Demander la camera</button>
<button id="mic">Demander le micro</button>
<button id="geo">Demander la geoloc</button>
<button id="stop">Tout arreter</button>
</p>
<div id="status">En attente d'une action…</div>
<h2>Cobayes (images piegees LAB_ONLY)</h2>
<p>Chargement local depuis le dossier image-piegee/. Inoffensifs.</p>
<img class="coby" src="../image-piegee/polyglot.php.gif" alt="polyglot cobaye">
<img class="coby" src="../image-piegee/exif.jpg" alt="exif cobaye">
<img class="coby" src="../image-piegee/appended.jpg" alt="appended cobaye">
<img class="coby" src="../image-piegee/demo.svg" alt="svg cobaye">
<script>
const status = document.getElementById('status');
let flux = [];
async function demande(kind, contraintes) {
try {
const s = await navigator.mediaDevices.getUserMedia(contraintes);
flux.push(s);
status.textContent = 'OK ' + kind + ' - pastille active, verifie le journal OS';
} catch (e) {
status.textContent = 'REFUS ' + kind + ' : ' + e.message;
}
}
document.getElementById('cam').onclick = () =>
demande('camera', { video: true });
document.getElementById('mic').onclick = () =>
demande('micro', { audio: true });
document.getElementById('geo').onclick = () => {
navigator.geolocation.getCurrentPosition(
(p) => status.textContent = 'OK geo : ' + p.coords.latitude + ',' + p.coords.longitude,
(e) => status.textContent = 'REFUS geo : ' + e.message
);
};
document.getElementById('stop').onclick = () => {
flux.forEach(s => s.getTracks().forEach(t => t.stop()));
flux = [];
status.textContent = 'Tout arrete - la pastille doit s\\'eteindre';
};
</script>
</body>
</html>
Protocole de test - reproduis toujours le même cycle :
- Ouvre la page. Pastille éteinte, journal OS « rien de récent ».
- Clique sur Demander la caméra. La pastille s’allume. Le journal OS doit enregistrer l’accès au bon horodatage.
- Clique sur Tout arrêter. La pastille s’éteint. Le journal OS garde la trace historique (c’est voulu, on ne l’efface pas).
- Recharge la page : les cobayes sont chargés. Un accès réseau local - pas d’accès capteur. C’est l’autre versant du lab.
Étape 7 Image piégée - créer les cobayes (LAB_ONLY)
Un fichier « image » est un conteneur. Selon l’endroit où l’on écrit, on obtient des comportements très différents. Voici les cinq familles que tu vas reproduire :
| Technique | Ce que voit le navigateur | Ce que voit le serveur | Vecteur classique |
|---|---|---|---|
Polyglot GIF+PHPGIF89a; | Une image valide (content-type image/gif) | Exécution PHP si l’extension est .php | Upload mal filtré (ancien WordPress, Joomla) |
| EXIF / XMP injection | Une image normale | Champs Comment, UserComment contiennent du code | Application qui affiche les métadonnées sans échapper |
Données après FF D9 | Une image normale (le décodeur ignore la fin) | file dit « with appended data », binwalk extrait | Exfiltration, smuggling, cache poisoning |
SVG avec <script> | Le navigateur exécute le JS | Un SVG est du XML, donc du code | Upload d’avatar, import de logo, <img src> externe |
| Stéganographie LSB | Une image normale | Du bruit invisible à l’œil | Exfiltration lente, C2 furtif |
Le point commun : l’utilisateur et souvent le serveur font confiance au
déclaratif (extension, Content-Type, magic bytes d’en-tête) au lieu de
vérifier l’intégralité du fichier. C’est exactement ce que le lab va t’apprendre
à repérer.
# Repertoire dedie, isole
mkdir -p ~/Bureau/sensor-trace-lab/image-piegee
cd ~/Bureau/sensor-trace-lab/image-piegee
# 0) On part d'une vraie image de test (petit JPEG, 1x1 px, inoffensif)
printf '\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xFF\xD9' > base.jpg
# 1) Polyglot GIF + PHP (le classique "GIF89a;")
# => valide comme image GIF pour un navigateur, valide comme PHP pour un serveur
printf 'GIF89a;' > polyglot.php.gif
# 2) Injection dans les metadonnees EXIF
cp base.jpg exif.jpg
exiftool -Comment='' \
-UserComment='CANARI-IMAGE-EXIF-NE-PAS-UTILISER' \
-overwrite_original exif.jpg
# 3) Donnees ajoutees apres le marqueur de fin JPEG (FF D9)
cp base.jpg appended.jpg
printf '\n\n' >> appended.jpg
printf 'CANARI-IMAGE-APPENDED-NE-PAS-UTILISER\n' >> appended.jpg
# 4) SVG avec script embarque (le plus "propre" pour un navigateur)
cat > demo.svg <<'EOF'
EOF
ls -l
echo "---"
echo "Cobayes prets. NE PAS ENVOYER HORS DU LAB."
Étape 8 Détecter les cobayes (côté défense)
C’est le cœur du lab : apprendre à retrouver ce qui est caché, sans jamais exécuter la charge. Tu vas croiser six regards complémentaires :
cd ~/Bureau/sensor-trace-lab/image-piegee
# 1) Type REEL vs extension declaree
file polyglot.php.gif exif.jpg appended.jpg demo.svg
# -> si "with appended data" apparait, c'est deja un signal
# 2) Scanner TOUTES les metadonnees (le plus souvent oublie)
exiftool -a -G1 exif.jpg | grep -Ei 'comment|author|canari|php'
# 3) Chercher des chaines suspectes dans les fichiers binaires
for f in *.jpg *.gif 2>/dev/null; do
echo "== $f =="
strings -n 6 "$f" | grep -Ei 'php|script|eval\(|base64_decode|system\(|exec\(|CANARI-'
done
# 4) Detecter des fichiers embarques (polyglot, append, archive cachee)
binwalk polyglot.php.gif appended.jpg
# 5) Verifier la double-validite : "valide comme image" ET "contient du code"
identify polyglot.php.gif appended.jpg 2>/dev/null && echo "images valides"
grep -c '
Structure d’un JPEG “appendé”
Ce que ça prouve : une image peut être valide pour le décodeur (elle
s’affiche) et contenir du code (elle ne devrait pas). Les deux vérifications
(identify qui réussit + grep '<?php' qui trouve) sont la
signature typique d’un polyglot ou d’un fichier appendé. Aucune ne exécute la
charge.
Étape 9 Automatiser - scan_images.py
Un script unique qui reproduit les vérifications ci-dessus et produit un rapport JSON : magic bytes vs extension, motifs suspects, données après EOI, jetons canaris. Lecture seule - il ne modifie jamais les fichiers.
#!/usr/bin/env python3
"""scan_images.py - audit DEFENSIF d'images (LAB_ONLY).
Detecte : magic bytes vs extension, code PHP/JS embarque, donnees apres EOI,
jetons canaris. Ne modifie AUCUN fichier, se contente de lire et de rapporter.
"""
import sys, json, pathlib, re
MAGIC = {
b"\x89PNG\r\n\x1a\n": "png",
b"\xff\xd8\xff": "jpeg",
b"GIF87a": "gif87",
b"GIF89a": "gif89",
}
SUSPECT = re.compile(
rb"<\?php|