Linux : tracer les accès au système de fichiers d’un programme
Sommaire
I. Présentation
Dans cet article, je vais vous présenter quelques méthodes pour tracer les accès et tentatives d'accès d'un programme sur les fichiers d'un système Linux.
Nous passerons dans un premier temps par un exécutable fait spécifiquement pour cette occasion, puis nous détaillerons ses résultats pour finir par reproduire son comportement par des commandes plus "manuelles".
II. Tracer les fichiers ouverts, pourquoi ?
Le fait d'avoir une liste précise des accès et tentatives d'accès aux fichiers et répertoires peut avoir plusieurs objectifs.
Cela peut par exemple permettre le débogage d'un programme ne fonctionnant pas comme attendu. Cela permet également de faciliter l'analyse d'un programme (malveillant ou non) afin de mieux comprendre son comportement, voire son impact sur le système lors de son exécution ou installation.
III. Lister les accès au système de fichiers avec whatfiles
Dans un premier temps, nous allons utiliser l'exécutable whatfiles. Il s'agit d'un exécutable disponible sur Github. Il permet de facilement tracer les appels système portant sur l'accès à un fichier ou un répertoire.
Un appel système (comprendre "système d'exploitation") ou syscall, désigne le moment où un programme s'interrompt pour demander au système d'exploitation d'accomplir pour lui une certaine tâche. Quelques appels système classiques :
- open, read, write et close qui permettent les manipulations sur le système de fichiers ;
- brk, sbrk, utilisés par malloc et free pour allouer et libérer de la mémoire.
Vous l'aurez compris, whatfiles va simplement surveiller les appels système relatifs au programme ciblé (ou PID attaché) et nous afficher ceux qui concernent l'accès au système de fichier. Son intérêt est qu'il est simple d'utilisation par rapport à d'autres commandes que nous verrons par la suite.
Pas d'installation requise, il suffit de télécharger le binaire correspondant à votre architecture (dans la plupart des cas : x64) dans l'espace Release du projet Github : https://github.com/spieglt/whatfiles/releases/latest. Une fois celui-ci sur votre système, nous allons nous donner les droits d'exécution :
$ chmod +x whatfiles_x64
Bien, tentons à présent de voir ce qu'il nous affiche pour un simple accès en lecture à un fichier :
$ ./whatfiles_x64 cat /etc/passwd
whatfiles log location: ./whatfiles1643273510.log
La liste des appels système pouvant parfois être verbeuse, whatfiles écrit sa sortie dans un fichier de journalisation dédié.
mode: read, file: /etc/ld.so.cache, syscall: openat(), PID: 21567, process: cat
mode: read, file: /lib/x86_64-linux-gnu/libc.so.6, syscall: openat(), PID: 21567, process: cat
mode: read, file: /usr/lib/locale/locale-archive, syscall: openat(), PID: 21567, process: cat
mode: read, file: /etc/passwd, syscall: openat(), PID: 21567, process: cat
Contrairement à ce que nous pouvions imaginer. L'exécution d'un simple cat génère l'accès à 4 fichiers et non un seul :
- /etc/ld.so.cache : sert à configurer les chemins de recherche des bibliothèques partagées. Une bibliothèque partagée est un fichier particulier qui contient une liste de fonctions, ou API, accessibles à tout programme en ayant besoin sans avoir à les réécrire. Nous pouvons donc imaginer que notre commande cat utilise une ou plusieurs bibliothèques partagées mises à disposition par le système.
- /lib/x86_64-linux-gnu/libc.so.6 : il s'agit de la bibliothèque en question (.so est l'extension classique des bibliothèques, à l'image des .dll sous Windows). La libc est la bibliothèque des fonctions classiques et standards des programmes C.
- /usr/lib/locale/locale-archive : fichier contenant les paramètres régionaux fournis par le système.
- /etc/passwd : le fichier ciblé par notre commande cat.
Si l'on analyse un peu plus précisément les appels système journalisés par whatfiles, nous voyons qu'il trace le processus ayant généré l'appel système et son PID, ainsi que le nom de l'appel système en question (openat()) et le flag, ou mode, utilisé (read).
Voici un exemple de l'analyse whatfiles pour une écriture de fichier :
$ ./whatfiles_x64 touch /tmp/x
whatfiles log location: ./whatfiles1643276163.log
$ cat ./whatfiles1643276163.log
mode: read, file: /etc/ld.so.cache, syscall: openat(), PID: 21968, process: touch
mode: read, file: /lib/x86_64-linux-gnu/libc.so.6, syscall: openat(), PID: 21968, process: touch
mode: read, file: /usr/lib/locale/locale-archive, syscall: openat(), PID: 21968, process: touch
mode: rd/wr, file: /tmp/x, syscall: openat(), PID: 21968, process: touch
Nous voyons ici que le fichier /tmp/x est créé en mode rd/rw plutôt qu'en mode read. Ces sorties sont en réalité des "traductions" des flags que l'on peut retrouver dans la documentation officielle de l'appel système open.
Voici l'extrait du code C responsable de cette "traduction" visant à rendre la compréhension et l'utilisation de whatfiles plus simple que d'autres commandes :
void get_mode(unsigned long long m, char *mode)
{
char *strings[] = {"read", "write", "rd/wr", "create"};
int modes[] = {O_RDONLY, O_WRONLY, O_RDWR, O_CREAT};
for (int i=0; i<4; i++) {
if (m & modes[i] || m == modes[i]) {
assert(strlen(strings[i]) < MODE_LEN);
strcpy(mode, strings[i]);
}
}
}
Autre option intéressante à propos de whatfiles, celui-ci peut être utilisé pour s'attacher à un processus déjà lancé par l'intermédiaire de son PID (Processus IDentifier) :
$ ps -edf
UID PID PPID C STIME TTY TIME CMD
mdy 22473 1421 33 10:48 ? 00:00:03 /usr/lib/firefox-esr/firefox-esr
$ ./whatfiles_x64 -p 22473
IV. Utilisation de strace
L'exécutable whatfiles est donc un bon outil, facile d'utilisation avec une sortie rapidement compréhensible. Si vous souhaitez aller plus loin, vous pouvez utiliser directement le programme strace qui permet de surveiller les appels système (quels qu'ils soient), puis filtrer ces derniers pour ne cibler que ceux qui concernent l'accès au système de fichier.
Strace est présent dans les dépôts officiels de la plupart des distributions Linux, mais rarement installé par défaut:
apt install strace
Pour cibler les appels système relatifs à l'accès au système de fichier, nous utiliserons l'option -e trace=creat,open,openat,unlink,unlinkat. Bien sur en fonction de vos besoins, vous pouvez ajouter ou supprimer des appels système. Pour détailler un peu ceux que nous venons de sélectionner :
- creat/open : ouvre ou crée un fichier sur le système de fichier
- openat : ouvre un fichier à partir du descripteur d'un répertoire (chemin relatif)
- unlink : supprime un nom de fichier du système de fichier et le fichier qui s'y réfère
- unlinkat : même chose que unlink, mais à partir du descripteur d'un répertoire (chemin relatif)
Voici un exemple de sortie lors de l'utilisation de cette commande pour l'accès en lecture à un fichier :
# strace -fe trace=creat,open,openat,unlink,unlinkat cat /etc/passwd
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/passwd", O_RDONLY) = 3
L'option -f nous permet ici de suivre les processus enfants lancés par le processus surveillé.
Nous retrouvons ici une sortie similaire à whatfiles, bien qu'un peu plus technique. Sont en effet affichés les appels système (openat) ainsi que les flags (O_RDONLY, O_CLOEXEC) dont les détails peuvent être obtenus ici : https://linux.die.net/man/3/open
Est également affiché le résultat de l'appel système (3). Pour comparaison, voici la sortie obtenue pour la lecture d'un fichier non existant :
# strace -fe trace=creat,open,openat,unlink,unlinkat cat /etc/spasswd
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/spasswd", O_RDONLY) = -1 ENOENT (Aucun fichier ou dossier de ce type)
cat: /etc/spasswdopenat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/share/locale/fr_FR.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Aucun fichier ou dossier de ce type)
openat(AT_FDCWD, "/usr/share/locale/fr_FR.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Aucun fichier ou dossier de ce type)
openat(AT_FDCWD, "/usr/share/locale/fr_FR/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Aucun fichier ou dossier de ce type)
openat(AT_FDCWD, "/usr/share/locale/fr.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Aucun fichier ou dossier de ce type)
openat(AT_FDCWD, "/usr/share/locale/fr.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Aucun fichier ou dossier de ce type)
openat(AT_FDCWD, "/usr/share/locale/fr/LC_MESSAGES/libc.mo", O_RDONLY) = 3
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache", O_RDONLY) = 3
: Aucun fichier ou dossier de ce type
Nous voyons en effet plusieurs résultats -1. Dont le premier est la tentative d'ouverture du fichier /etc/spasswd. S'en suivent plusieurs ouvertures de fichier de type .mo dans le dossier /usr/share/locale. On peut supposer que ces ouvertures correspondent à une tentative de retrouver le message No file or directory en français. Qui est d'ailleurs affiché en dernière ligne après une tentative réussie d'ouverture d'un fichier .mo.
Également pour comparaison, voici la liste des appels système relatifs à l'accès au système de fichier lors de l'écriture d'un nouveau fichier :
# strace -fe trace=creat,open,openat,unlink,unlinkat touch /tmp/y
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/tmp/y", O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666) = 3
+++ exited with 0 +++
Nous voyons ici une différence au niveau des flags utilisés pour l'écriture du fichier et l'on reconnait notamment le flag O_WRONLY (pour write only). Nous avons donc une description plutôt complète des accès au système de fichiers réalisés par cette commande.
Nous avons dans cet article découvert deux méthodes différentes pour analyser et comprendre le comportement d'un programme au niveau du système de fichier. Je vous invite à creuser les options de strace et les différents appels système existants, il y a plein de choses intéressantes à y découvrir :).
Merci, ça servira