Questa nota è stata scritta nel 2014, ma proprio in quel periodo ho subito delle repressioni su Habr e non è mai stata pubblicata. Durante il periodo di blocco l'ho dimenticata, ma ora l'ho trovata tra le bozze. Pensavo di eliminarla, ma magari può tornare utile a qualcuno.

In sintesi, una piccola lettura per amministratori del venerdì sul tema della ricerca di "funzionamento" LD_PRELOAD.
1. Piccola digressione per chi non è familiare con la sostituzione delle funzioni
Gli altri possono passare direttamente a p.2.
Iniziamo con un esempio classico:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
srand (time(NULL));
for(int i=0; i<5; i++){
printf ("%dn", rand()%100);
}
}
Compiliamo senza alcun flag:
$ gcc ./ld_rand.c -o ld_rand
E, come prevedibile, otteniamo 5 numeri casuali minori di 100:
$ ./ld_rand
53
93
48
57
20
Ma immaginiamo di non avere il codice sorgente del programma e di dover cambiare il comportamento.
Creeremo la nostra libreria con il nostro prototipo di funzione, ad esempio:
int rand(){
return 42;
}
$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so
E ora la nostra scelta casuale è piuttosto prevedibile:
# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42
Questo trucco sembra ancor più impressionante se prima esportiamo la nostra libreria tramite
$ export LD_PRELOAD=$PWD/ld_rand.so
o eseguiamo prima
# echo "$PWD/ld_rand.so" > /etc/ld.so.preload
per poi avviare il programma in modalità normale. Non abbiamo modificato nemmeno una riga nel codice del programma stesso, ma ora il suo comportamento dipende da una piccola funzione nella nostra libreria. Inoltre, al momento della scrittura del programma, il falso rand non esisteva nemmeno.
Cosa ha spinto il nostro programma a utilizzare il falso rand? Разберем по шагам.
Quando l'applicazione viene avviata, vengono caricate alcune librerie specifiche che contengono le funzioni necessarie al programma. Possiamo esaminarle usando ldd:
# ldd ./ld_rand
linux-vdso.so.1 (0x00007ffc8b1f3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe3da8af000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe3daa7e000)
Questa lista può variare a seconda della versione del sistema operativo, ma deve necessariamente includere il file libc.so. È proprio questa libreria a fornire le chiamate di sistema e le funzioni di base, come open, malloc, printf e così via. Il nostro rand è anche tra di esse. Assicuriamoci di questo:
# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand
Controlliamo se il set di librerie cambia quando utilizziamo LD_PRELOAD
# LD_PRELOAD=$PWD/ld_rand.so ldd ./ld_rand
linux-vdso.so.1 (0x00007ffea52ae000)
/scripts/c/ldpreload/ld_rand.so (0x00007f690d3f9000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f690d230000)
/lib64/ld-linux-x86-64.so.2 (0x00007f690d405000)
Si scopre che la variabile impostata LD_PRELOAD fa caricare la nostra ld_rand.so anche se il programma non la richiede. E, poiché la nostra funzione "rand" viene caricata prima di rand di libc.so, essa detta legge.
Ok, siamo riusciti a sostituire la funzione originale, ma come possiamo fare in modo che il suo funzionamento rimanga e che vengano aggiunte alcune azioni. Modifichiamo il nostro random:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
typedef int (*orig_rand_f_type)(void);
int rand()
{
/* Выполняем некий код */
printf("Evil injected coden");
orig_rand_f_type orig_rand;
orig_rand = (orig_rand_f_type)dlsym(RTLD_NEXT,"rand");
return orig_rand();
}
Qui come nostra "aggiunta" stampiamo solo una riga di testo, dopo di che creiamo un puntatore alla funzione originale rand. Per ottenere l'indirizzo di questa funzione avremo bisogno dlsym — è una funzione della libreria libdl, che troverà il nostro rand nello stack delle librerie dinamiche. Dopo di che chiameremo questa funzione e restituiremo il suo valore. Pertanto, dovremo aggiungere «-ldl» nella costruzione:
$ gcc -ldl -shared -fPIC ./o_rand_evil.c -o ld_rand_evil.so
$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Codice iniettato malevolo
66
Codice iniettato malevolo
28
Codice iniettato malevolo
93
Codice iniettato malevolo
93
Codice iniettato malevolo
95
E il nostro programma utilizza «nativo» rand, eseguendo prima alcune operazioni non necessarie.
2. Le fatiche della ricerca
Sapendo della minaccia potenziale, vogliamo scoprire che preload è stato eseguito. È chiaro che il modo migliore per la rilevazione è inserirlo nel kernel, ma ero interessato in particolare alle opzioni per la determinazione nello spazio utente.
Di seguito seguiranno coppie di soluzioni per la rilevazione e le loro confutazioni.
2.1. Iniziamo con qualcosa di semplice
Come già detto, è possibile specificare la libreria da caricare utilizzando la variabile LD_PRELOAD o scrivendola nel file /etc/ld.so.preload. Creiamo due semplici rilevatori.
Il primo — per verificare la variabile d'ambiente impostata:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main()
{
char* pGetenv = getenv("LD_PRELOAD");
pGetenv != NULL ?
printf("LD_PRELOAD (getenv) [+]n"):
printf("LD_PRELOAD (getenv) [-]n");
}
Il secondo — per controllare l'apertura di file:
#include <stdio.h>
#include <fcntl.h>
int main()
{
open("/etc/ld.so.preload", O_RDONLY) != -1 ?
printf("LD_PRELOAD (open) [+]n"):
printf("LD_PRELOAD (open) [-]n");
}
Carichiamo le librerie:
$ export LD_PRELOAD=$PWD/ld_rand.so
$ echo "$PWD/ld_rand.so" > /etc/ld.so.preload
$ ./detect_base_getenv
LD_PRELOAD (getenv) [+]
$ ./detect_base_open
LD_PRELOAD (open) [+]
Qui e oltre, [+] indica un rilevamento riuscito.
Pertanto, [-] indica un bypass del rilevamento.
Quanto è efficace un tale rilevatore? Prima affrontiamo la variabile d'ambiente:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <dlfcn.h>
char* (*orig_getenv)(const char *) = NULL;
char* getenv(const char *name)
{
if(!orig_getenv) orig_getenv = dlsym(RTLD_NEXT, "getenv");
if(strcmp(name, "LD_PRELOAD") == 0) return NULL;
return orig_getenv(name);
}
$ gcc -shared -fpic -ldl ./ld_undetect_getenv.c -o ./ld_undetect_getenv.so
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_base_getenv
LD_PRELOAD (getenv) [-]
Analogamente ci liberiamo anche del controllo open:
#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <errno.h>
int (*orig_open)(const char*, int oflag) = NULL;
int open(const char *path, int oflag, ...)
{
char real_path[256];
if(!orig_open) orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0){
errno = ENOENT;
return -1;
}
return orig_open(path, oflag);
}
$ gcc -shared -fpic -ldl ./ld_undetect_open.c -o ./ld_undetect_open.so
$ LD_PRELOAD=./ld_undetect_open.so ./detect_base_open
LD_PRELOAD (open) [-]
Sì, qui possono essere utilizzati altri metodi di accesso al file, come open64, stat ecc., ma, sostanzialmente, per ingannarli sono necessarie le stesse 5-10 righe di codice.
2.2. Andiamo avanti
Qui sopra abbiamo usato getenv() per ottenere il valore LD_PRELOAD, ma c'è un modo più «basso livello» per accedere alle variabili ENV. Non useremo funzioni intermedie, ma ci rivolgiamo direttamente all'array **environ, dove è memorizzata una copia dell'ambiente:
#include <stdio.h>
#include <string.h>
extern char **environ;
int main(int argc, char **argv) {
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++)
{
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL)
{
printf("LD_PRELOAD (**environ) [+]n");
return 0;
}
}
printf("LD_PRELOAD (**environ) [-]n");
return 0;
}
Poiché qui leggiamo i dati direttamente dalla memoria, tale chiamata non può essere intercettata, e il nostro undetect_getenv non ostacola più la rilevazione di intrusioni.
$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]
Sembrerebbe che il problema sia risolto? È solo l'inizio.
Dopo che il programma è stato avviato, il valore della variabile LD_PRELOAD in memoria non è più necessario per i pirati, quindi si può considerare e rimuovere prima di eseguire qualsiasi istruzione. Certo, modificare un array in memoria è, almeno, uno stile di programmazione sbagliato, ma può questo fermare qualcuno che non ha davvero buone intenzioni nei nostri confronti?
Per questo avremo bisogno di creare la nostra funzione fittizia init(), in cui intercetteremo il LD_PRELOAD e lo passeremo al nostro linker:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dlfcn.h>
#include <stdlib.h>
extern char **environ;
char *evil_env;
int (*orig_execve)(const char *path, char *const argv[], char *const envp[]) = NULL;
// Создаём фейковую версию init
// которая будет вызвана при загрузке программы
// до выполнения каких-либо инструкций
void evil_init()
{
// Сначала сохраним текущее значение LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
// Избавляемся от текущего LD_PRELOAD
unsetenv(env);
break;
}
}
}
int execve(const char *path, char *const argv[], char *const envp[])
{
int i = 0, j = 0, k = -1, ret = 0;
char** new_env;
if(!orig_execve) orig_execve = dlsym(RTLD_NEXT,"execve");
// Проверям не существует ли других установленных LD_PRELOAD
for(i = 0; envp[i]; i++){
if(strstr(envp[i], "LD_PRELOAD")) k = i;
}
// Если LD_PRELOAD не было установлено до нас, то добавим его
if(k == -1){
k = i;
i++;
}
// Создаём новое окружение
new_env = (char**) malloc((i+1)*sizeof(char*));
// Копируем старое окружение, за исключением LD_PRELOAD
for(j = 0; j < i; j++) {
// перезаписываем или создаём LD_PRELOAD
if(j == k) {
new_env[j] = (char*) malloc(256);
strcpy(new_env[j], "LD_PRELOAD=");
strcat(new_env[j], evil_env);
}
else new_env[j] = (char*) envp[j];
}
new_env[i] = NULL;
ret = orig_execve(path, argv, new_env);
free(new_env[k]);
free(new_env);
return ret;
}
Eseguiamo, verifichiamo:
$ gcc -shared -fpic -ldl -Wl,-init,evil_init .\/ld_undetect_environ.c -o .\/ld_undetect_environ.so
$ LD_PRELOAD=.\/ld_undetect_environ.so .\/detect_environ
LD_PRELOAD (**environ) [-]
2.3. \/proc\/self\/
Tuttavia, la memoria non è l'ultima posizione dove si può scoprire una sostituzione LD_PRELOAD, ce ne sono anche altre /proc/. Iniziamo con l'ovvio /proc/{PID}/environ.
In realtà, esiste una soluzione universale per l'undetect **environ e /proc/self/environ. Il problema risiede nel comportamento "non corretto" di unsetenv(env).
la versione corretta
void evil_init()
{
\/\/ Prima salviamo il valore attuale di LD_PRELOAD
static const char *ldpreload = "LD_PRELOAD";
int len = strlen(getenv(ldpreload));
evil_env = (char*) malloc(len+1);
strcpy(evil_env, getenv(ldpreload));
int i;
char env[] = "LD_PRELOAD";
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++) {
char * pch;
pch = strstr(environ[i],env);
if(pch != NULL) {
\/\/ Liberiamoci del corrente LD_PRELOAD
\/\/unsetenv(env);
\/\/ Invece di unset, semplicemente azzeriamo la nostra variabile
for(int j = 0; environ[i][j] != ' '; j++) environ[i][j] = ' ';
break;
}
}
}
$ gcc -shared -fpic -ldl -Wl,-init,evil_init .\/ld_undetect_environ_2.c -o .\/ld_undetect_environ_2.so
$ (LD_PRELOAD=.\/ld_undetect_environ_2.so cat \/proc\/self\/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Ma immaginiamo che non lo abbiamo trovato e /proc/self/environ contiene dati "problematici".
Iniziamo con il nostro precedente "camuffamento":
$ (LD_PRELOAD=.\/ld_undetect_environ.so cat \/proc\/self\/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
LD_PRELOAD=.\/ld_undetect_environ.so
cat usa per aprire il file la stessa open(), quindi la soluzione è simile a quella già fatta nel punto 2.1, ma ora creiamo un file temporaneo dove copiamo i valori della memoria reale senza le righe contenenti LD_PRELOAD.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#define BUFFER_SIZE 256
int (*orig_open)(const char*, int oflag) = NULL;
char *soname = "fakememory_preload.so";
char *sstrstr(char *str, const char *sub)
{
int i, found;
char *ptr;
found = 0;
for(ptr = str; *ptr != ' '; ptr++) {
found = 1;
for(i = 0; found == 1 && sub[i] != ' '; i++){
if(sub[i] != ptr[i]) found = 0;
}
if(found == 1)
break;
}
if(found == 0)
return NULL;
return ptr + i;
}
void fakeMaps(char *original_path, char *fake_path, char *pattern)
{
int fd;
char buffer[BUFFER_SIZE];
int bytes = -1;
int wbytes = -1;
int k = 0;
pid_t pid = getpid();
int fh;
if ((fh=orig_open(fake_path,O_CREAT|O_WRONLY))==-1) {
printf("LD: Cannot open write-file [%s] (%d) (%s)n", fake_path, errno, strerror(errno));
exit (42);
}
if((fd=orig_open(original_path, O_RDONLY))==-1) {
printf("LD: Cannot open read-file.n");
exit(42);
}
do
{
char t = 0;
bytes = read(fd, &t, 1);
buffer[k++] = t;
//printf("%c", t);
if(t == ' ') {
//printf("n");
if(!sstrstr(buffer, "LD_PRELOAD")) {
if((wbytes = write(fh,buffer,k))==-1) {
//printf("write errorn");
}
else {
//printf("writed %dn", wbytes);
}
}
k = 0;
}
}
while(bytes != 0);
close(fd);
close(fh);
}
int open(const char *path, int oflag, ...)
{
char real_path[PATH_MAX], proc_path[PATH_MAX], proc_path_0[PATH_MAX];
pid_t pid = getpid();
if(!orig_open)
orig_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
snprintf(proc_path, PATH_MAX, "/proc/%d/environ", pid);
if(strcmp(real_path, proc_path) == 0) {
snprintf(proc_path, PATH_MAX, "/tmp/%d.fakemaps", pid);
realpath(proc_path_0, proc_path);
fakeMaps(real_path, proc_path, soname);
return orig_open(proc_path, oflag);
}
return orig_open(path, oflag);
}
E questo passaggio è completato:
$ (LD_PRELOAD=.\/ld_undetect_proc_environ.so cat \/proc\/self\/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$
Il prossimo luogo ovvio è /proc/self/maps. Non ha senso soffermarsi su di esso. La soluzione è assolutamente identica alla precedente: copiamo i dati dal file escludendo le righe tra libc.so e ld.so.
2.4. Variante di Chokepoint
Questa soluzione mi è piaciuta particolarmente per la sua semplicità. Confrontiamo gli indirizzi delle funzioni caricate direttamente da libc, e gli indirizzi "NEXT".
#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#define LIBC "/lib/x86_64-linux-gnu/libc.so.6"
int main(int argc, char *argv[]) {
void *libc = dlopen(LIBC, RTLD_LAZY); // Open up libc directly
char *syscall_open = "open";
int i;
void *(*libc_func)();
void *(*next_func)();
libc_func = dlsym(libc, syscall_open);
next_func = dlsym(RTLD_NEXT, syscall_open);
if (libc_func != next_func) {
printf("LD_PRELOAD (syscall - %s) [+]n", syscall_open);
printf("Libc address: %pn", libc_func);
printf("Next address: %pn", next_func);
}
else {
printf("LD_PRELOAD (syscall - %s) [-]n", syscall_open);
}
return 0;
}
Carichiamo la libreria con l'intercettazione "open()" e verifichiamo:
$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Indirizzo Libc: 0x7fa86893b160
Indirizzo successivo: 0x7fa868a26135
La smentita si è rivelata ancora più semplice:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
extern void * _dl_sym (void *, const char *, void *);
void * dlsym (void * handle, const char * symbol)
{
return _dl_sym (handle, symbol, dlsym);
}
# LD_PRELOAD=./ld_undetect_chokepoint.so ./detect_chokepoint
LD_PRELOAD (syscall - open) [-]
2.5. Syscall
Sembra che sia tutto qui, ma dai un'altra occhiata. Se inviamo la chiamata di sistema direttamente al kernel, questo ci permetterà di bypassare l'intero processo di intercettazione. La soluzione qui sotto, ovviamente, dipende dall'architettura (x86_64). Proviamo a realizzare per la rilevazione dell'apertura ld.so.preload.
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFFER_SIZE 256
int syscall_open(char *path, long oflag)
{
int fd = -1;
__asm__ (
"mov $2, %%rax;" // Open syscall number
"mov %1, %%rdi;" // Address of our string
"mov %2, %%rsi;" // Open mode
"mov $0, %%rdx;" // No create mode
"syscall;" // Straight to ring0
"mov %%eax, %0;" // Returned file descriptor
:"=r" (fd)
:"m" (path), "m" (oflag)
:"rax", "rdi", "rsi", "rdx"
);
return fd;
}
int main()
{
syscall_open("/etc/ld.so.preload", O_RDONLY) > 0 ?
printf("LD_PRELOAD (open syscall) [+]n"):
printf("LD_PRELOAD (open syscall) [-]n");
}
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
E questo compito ha una soluzione. Estratto da man‘a:
ptrace è uno strumento che consente a un processo genitore di osservare e controllare il flusso di un altro processo, visualizzando e modificando i suoi dati e registri. Questa funzione viene solitamente utilizzata per creare punti di interruzione in un programma di debug e per monitorare le chiamate di sistema.
Un processo genitore può avviare il tracciamento, chiamando prima la funzione fork(2), e quindi il processo discendente risultante può eseguire PTRACE_TRACEME, dopo il quale (di solito) segue l'esecuzione di exec(3). D'altra parte, un processo genitore può avviare il debug di un processo esistente utilizzando PTRACE_ATTACH.
Durante il tracciamento, il processo discendente si interrompe ogni volta che riceve un segnale, anche se quel segnale viene ignorato. (L'eccezione è SIGKILL, che funziona normalmente). Il processo genitore verrà avvisato di questo quando chiama wait(2), dopo di che può visualizzare e modificare il contenuto del processo discendente prima della sua esecuzione. Successivamente, il processo genitore consente al discendente di continuare a lavorare, a volte ignorando il segnale inviato o inviando invece un altro segnale).
Pertanto, la soluzione consiste nel monitorare il processo, fermandolo prima di ogni chiamata di sistema e, se necessario, reindirizzando il flusso alla funzione di trap.
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <asm/unistd.h>
#if defined(__x86_64__)
#define REG_SYSCALL ORIG_RAX
#define REG_SP rsp
#define REG_IP rip
#endif
long NOHOOK = 0;
long evil_open(const char *path, long oflag, long cflag)
{
char real_path[PATH_MAX], maps_path[PATH_MAX];
long ret;
pid_t pid;
pid = getpid();
realpath(path, real_path);
if(strcmp(real_path, "/etc/ld.so.preload") == 0)
{
errno = ENOENT;
ret = -1;
}
else
{
NOHOOK = 1; // Entering NOHOOK section
ret = open(path, oflag, cflag);
}
// Exiting NOHOOK section
NOHOOK = 0;
return ret;
}
void init()
{
pid_t program;
// Форкаем дочерний процесс
program = fork();
if(program != 0) {
int status;
long syscall_nr;
struct user_regs_struct regs;
// Подключаемся к дочернему процессу
if(ptrace(PTRACE_ATTACH, program) != 0) {
printf("Failed to attach to the program.n");
exit(1);
}
waitpid(program, &status, 0);
// Отслеживаем только SYSCALLs
ptrace(PTRACE_SETOPTIONS, program, 0, PTRACE_O_TRACESYSGOOD);
while(1) {
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
if(WIFEXITED(status) || WIFSIGNALED(status)) break;
else if(WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP|0x80) {
// Получаем номер системного вызова
syscall_nr = ptrace(PTRACE_PEEKUSER, program, sizeof(long)*REG_SYSCALL);
if(syscall_nr == __NR_open) {
// Читаем слово из памяти дочернего процесса
NOHOOK = ptrace(PTRACE_PEEKDATA, program, (void*)&NOHOOK);
// Перехватываем вызов
if(!NOHOOK) {
// Копируем регистры дочернего процесса
// в переменную regs родительского
ptrace(PTRACE_GETREGS, program, 0, ®s);
// Push return address on the stack
regs.REG_SP -= sizeof(long);
// Копируем слово в память дочернего процесса
ptrace(PTRACE_POKEDATA, program, (void*)regs.REG_SP, regs.REG_IP);
// Устанавливаем RIP по адресу evil_open
regs.REG_IP = (unsigned long) evil_open;
// Записываем состояние регистров процесса
ptrace(PTRACE_SETREGS, program, 0, ®s);
}
}
ptrace(PTRACE_SYSCALL, program, 0, 0);
waitpid(program, &status, 0);
}
}
exit(0);
}
else {
sleep(0);
}
}
Controlliamo:
$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]
+0-0=5
Un enorme grazie
, i cui articoli, sorgenti e commenti hanno fatto molto di più di quanto io abbia potuto fare affinché questa nota apparisse qui.
Fonte: habr.com
