Perché non puoi/non vuoi usare il trucco LD_PRELOAD?
Esempio di codice qui:
/*
* File: soft_atimes.c
* Author: D.J. Capelis
*
* Compile:
* gcc -fPIC -c -o soft_atimes.o soft_atimes.c
* gcc -shared -o soft_atimes.so soft_atimes.o -ldl
*
* Use:
* LD_PRELOAD="./soft_atimes.so" command
*
* Copyright 2007 Regents of the University of California
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>
extern int errorno;
int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;
int open(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open) {
_open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
}
if(flags & O_CREAT)
return _open(pathname, flags | O_NOATIME, mode);
else
return _open(pathname, flags | O_NOATIME, 0);
}
int open64(const char * pathname, int flags, mode_t mode)
{
if (NULL == _open64) {
_open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
}
if(flags & O_CREAT)
return _open64(pathname, flags | O_NOATIME, mode);
else
return _open64(pathname, flags | O_NOATIME, 0);
}
Da quello che ho capito ... è praticamente il trucco LD_PRELOAD o un modulo del kernel. Non c'è molta via di mezzo, a meno che tu non voglia eseguirlo sotto un emulatore che può intercettare la tua funzione o eseguire la riscrittura del codice sul binario effettivo per intercettare la tua funzione.
Supponendo che tu non possa modificare il programma e non puoi (o non vuoi) modificare il kernel, l'approccio LD_PRELOAD è il migliore, supponendo che la tua applicazione sia abbastanza standard e non sia effettivamente quella che sta tentando maliziosamente di superare la tua intercettazione (In tal caso avrai bisogno di una delle altre tecniche.)
Valgrind può essere utilizzato per intercettare qualsiasi chiamata di funzione. Se hai bisogno di intercettare una chiamata di sistema nel tuo prodotto finito, questo non sarà utile. Tuttavia, se provi a intercettare durante lo sviluppo, può essere molto utile. Ho utilizzato spesso questa tecnica per intercettare le funzioni di hashing in modo da poter controllare l'hash restituito a scopo di test.
Nel caso in cui non lo sapessi, Valgrind viene utilizzato principalmente per trovare perdite di memoria e altri errori relativi alla memoria. Ma la tecnologia sottostante è fondamentalmente un emulatore x86. Emula il tuo programma e intercetta le chiamate a malloc/free ecc. La cosa buona è che non hai bisogno di ricompilare per usarlo.
Valgrind ha una caratteristica che chiamano Function Wrapping , utilizzato per controllare l'intercettazione delle funzioni. Vedere la sezione 3.2 del manuale Valgrind per i dettagli. Puoi configurare il wrapping delle funzioni per qualsiasi funzione che ti piace. Una volta intercettata la chiamata, viene richiamata la funzione alternativa fornita.
Per prima cosa eliminiamo alcune non risposte che altre persone hanno dato:
- Usa
LD_PRELOAD
. Sì, hai detto "Oltre aLD_PRELOAD
..." nella domanda, ma a quanto pare non è sufficiente per alcune persone. Questa non è una buona opzione perché funziona solo se il programma utilizza libc, il che non è necessariamente il caso. - Utilizza Systemtap. Sì, hai detto "Oltre a ... Linux Kernel Modules" nella domanda, ma a quanto pare non è abbastanza per alcune persone. Questa non è una buona opzione perché devi caricare un modulo kernal personalizzato che è un grosso problema e richiede anche root.
- Valgrind. Questo funziona, ma funziona simulando la CPU, quindi è molto lento e molto complicato. Va bene se lo stai facendo solo per il debug una tantum. Non proprio un'opzione se stai facendo qualcosa degno di una produzione.
- Varie cose di controllo delle chiamate di sistema. Non credo che la registrazione delle chiamate di sistema conti come "intercettarle". Chiaramente vogliamo modificare i parametri della chiamata di sistema / i valori restituiti o reindirizzare il programma attraverso qualche altro codice.
Tuttavia ci sono altre possibilità non menzionate qui ancora. Nota che sono nuovo in tutte queste cose e non ho ancora provato nulla di tutto ciò, quindi potrei sbagliarmi su alcune cose.
Riscrivi il codice
In teoria potresti usare una sorta di caricatore personalizzato che riscrive le istruzioni syscall per passare invece a un gestore personalizzato. Ma penso che sarebbe un vero incubo da implementare.
ksonde
I kprobes sono una sorta di sistema di strumentazione del kernel. Hanno solo accesso in sola lettura a qualsiasi cosa, quindi non puoi usarli per intercettare le chiamate di sistema, ma solo registrarle.
ptrace
ptrace è l'API che i debugger come GDB usano per fare il loro debug. C'è un PTRACE_SYSCALL
opzione che sospenderà l'esecuzione appena prima/dopo le chiamate di sistema. Da lì puoi fare praticamente quello che vuoi nello stesso modo in cui lo fa GDB. Ecco un articolo su come modificare i parametri di syscall usando ptrace. Tuttavia, a quanto pare ha un sovraccarico elevato.
Secocomp
Seccomp è un sistema progettato per permetterti di filtrare syscalls. Non puoi modificare gli argomenti, ma puoi bloccarli o restituire errori personalizzati. I filtri Seccomp sono programmi BPF. Se non hai familiarità, sono fondamentalmente programmi arbitrari che gli utenti possono eseguire in una VM nello spazio del kernel. Questo evita il cambio di contesto utente/kernel che li rende più veloci di ptrace.
Anche se non puoi modificare gli argomenti direttamente dal tuo programma BPF, puoi restituisce SECCOMP_RET_TRACE
che attiverà un ptrace
ing genitore a rompere. Quindi è fondamentalmente uguale a PTRACE_SYSCALL
tranne che puoi eseguire un programma nello spazio del kernel per decidere se vuoi effettivamente intercettare una chiamata di sistema in base ai suoi argomenti. Quindi dovrebbe essere più veloce se vuoi solo intercettare alcune chiamate di sistema (ad es. open()
con percorsi specifici).
Penso che questa sia probabilmente l'opzione migliore. Ecco un articolo a riguardo dello stesso autore di quello sopra. Nota che usano il classico BPF invece di eBPF, ma immagino che tu possa usare anche eBPF.
Modifica:in realtà puoi usare solo BPF classico, non eBPF. C'è un articolo di LWN a riguardo.
Ecco alcune domande correlate. Vale sicuramente la pena leggere il primo.
- eBPF può modificare il valore restituito o i parametri di una chiamata di sistema?
- Intercetta solo chiamate di sistema con PTRACE_SINGLESTEP
- È un buon modo per intercettare le chiamate di sistema?
- Modo minimo per intercettare le chiamate di sistema senza modificare il kernel
C'è anche un buon articolo sulla manipolazione delle chiamate di sistema tramite ptrace qui.
Alcune applicazioni possono ingannare strace/ptrace per non funzionare, quindi l'unica vera opzione che ho avuto è usare systemtap
Systemtap può intercettare una serie di chiamate di sistema, se necessario, grazie alla corrispondenza dei caratteri jolly. Systemtap non è C, ma un linguaggio separato. In modalità di base, il systemtap dovrebbe impedirti di fare cose stupide, ma può anche essere eseguito in "modalità esperto" che ricade nel consentire a uno sviluppatore di utilizzare C se necessario.
Non richiede di patchare il kernel (o almeno non dovrebbe), e una volta che un modulo è stato compilato, puoi copiarlo da una casella di test/sviluppo e inserirlo (tramite insmod) su un sistema di produzione.
Devo ancora trovare un'applicazione Linux che abbia trovato un modo per aggirare/evitare di essere beccato da systemtap.