À la recherche de LD_PRELOAD

Cette note a été écrite en 2014, mais j'étais justement sous répression sur Habr et elle n'a pas vu le jour. Pendant ma période d'interdiction, j'ai oublié, et maintenant je l'ai retrouvée dans mes brouillons. J'ai pensé à la supprimer, mais peut-être qu'elle pourra aider quelqu'un.

À la recherche de LD_PRELOAD

En résumé, une petite lecture administrative pour ce vendredi sur le thème de la recherche de "chargement" LD_PRELOAD.

1. Petite digression pour ceux qui ne connaissent pas le remplacement de fonctions

Les autres peuvent passer directement à point 2.

Commençons par un exemple classique :

#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);
  }
}

Nous compilons sans aucun drapeau :

$ gcc ./ld_rand.c -o ld_rand

Et, comme prévu, nous obtenons 5 nombres aléatoires inférieurs à 100 :

$ ./ld_rand
53
93
48
57
20

Mais imaginons que nous n’ayons pas le code source du programme, mais que nous devions modifier son comportement.

Créons notre propre bibliothèque avec notre propre prototype de fonction, par exemple :

int rand(){
  return 42;
}

$ gcc -shared -fPIC ./o_rand.c -o ld_rand.so

Et maintenant, notre choix aléatoire est tout à fait prévisible :

# LD_PRELOAD=$PWD/ld_rand.so ./ld_rand
42
42
42
42
42

Ce tour de magie semble encore plus impressionnant si nous exportons d'abord notre bibliothèque via

$ export LD_PRELOAD=$PWD/ld_rand.so

ou l'exécutons d'abord

# echo "$PWD/ld_rand.so" > /etc/ld.so.preload

et ensuite lançons le programme en mode normal. Nous n'avons pas modifié une seule ligne du code du programme lui-même, mais son comportement dépend maintenant d'une petite fonction dans notre bibliothèque. De plus, à l'époque de l'écriture de ce programme, le faux rand n'existait même pas.

Qu'est-ce qui a poussé notre programme à utiliser le faux rand? Разберем по шагам.
Lorsque l'application se lance, certaines bibliothèques nécessaires au programme sont chargées. Nous pouvons les voir en utilisant 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)

Cette liste peut varier en fonction de la version de l'OS, mais il doit y avoir nécessairement un fichier libc.so. C'est cette bibliothèque qui fournit les appels système et les fonctions de base, telles que open, malloc, printf etc. Notre rand fait également partie de celles-ci. Vérifions cela :

# nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep " rand$"
000000000003aef0 T rand

Voyons si l'ensemble des bibliothèques change lorsque nous utilisons 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)

Il s'avère que la variable installée LD_PRELOAD force notre ld_rand.so à se charger, même si le programme ne la demande pas. Et, puisque notre fonction "rand" se charge avant que rand à partir de libc.sole fasse, elle a le contrôle.

Ok, nous avons réussi à remplacer la fonction native, mais comment faire pour que sa fonctionnalité reste intacte tout en ajoutant certaines actions ? Modifions notre 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();
}

Ici, en tant qu'« ajout », nous imprimons simplement une ligne de texte, puis nous créons un pointeur vers la fonction originale. rand. Pour obtenir l'adresse de cette fonction, nous aurons besoin de dlsym — c'est une fonction de la bibliothèque libdl, qui trouvera notre rand dans la pile des bibliothèques dynamiques. Ensuite, nous appellerons cette fonction et retournerons sa valeur. Par conséquent, nous devrons ajouter «-ldl» lors de la compilation :

$ gcc -ldl -shared -fPIC ./o_rand_evil.c -o ld_rand_evil.so

$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Code malveillant injecté
66
Code malveillant injecté
28
Code malveillant injecté
93
Code malveillant injecté
93
Code malveillant injecté
95

Et notre programme utilise le «natif» rand, après avoir exécuté certaines actions indésirables.

2. La douleur de la recherche

Sachant qu'il y a une menace potentielle, nous voulons détecter ce qui preload a été exécuté. Il est évident que le meilleur moyen de détection est de l'intégrer dans le noyau, mais j'étais intéressé par des options pour le définir en espace utilisateur.

Ensuite, nous présenterons des solutions pour la détection et leur réfutation.

2.1. Commençons par le simple

Comme mentionné précédemment, il est possible d'indiquer la bibliothèque à charger à l'aide de la variable LD_PRELOAD ou en l'inscrivant dans le fichier /etc/ld.so.preload. Créons deux détecteurs très simples.

Le premier — pour vérifier la variable d'environnement définie :

#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");
}

Le second — pour vérifier l'ouverture de fichier :

#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");
}

Chargeons les bibliothèques :

$ 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) [+]

Ici et par la suite, [+] indique une détection réussie.
Par conséquent, [-] signifie contournement de la détection.

Quelle est l'efficacité d'un tel détecteur ? Commençons par la variable d'environnement :

#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) [-]

De même, nous nous débarrassons de la vérification 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) [-]

Oui, d'autres méthodes d'accès à des fichiers peuvent être utilisées ici, telles que open64, stat etc., mais en fait, il faut les mêmes 5-10 lignes de code pour les tromper.

2.2. Passons à autre chose

Ci-dessus, nous avons utilisé getenv() pour obtenir la valeur LD_PRELOAD, mais il existe également une méthode «plus bas niveau» pour accéder à ENV-variables. N'utilisons pas de fonctions intermédiaires, mais accédons au tableau **environ, où une copie de l'environnement est stockée :

#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;
}

Puisque nous lisons directement les données de la mémoire ici, cette méthode ne peut pas être interceptée, et notre undetect_getenv n'empêche déjà pas de détecter l'intrusion.

$ LD_PRELOAD=./ld_undetect_getenv.so ./detect_environ
LD_PRELOAD (**environ) [+]

On pourrait penser que le problème est résolu ? Cela ne fait que commencer.

Une fois le programme lancé, la valeur de la variable LD_PRELOAD n'est plus nécessaire aux pirates, c'est-à-dire qu'on peut la considérer et la supprimer avant d'exécuter toute instruction. Bien sûr, modifier un tableau en mémoire est au minimum un mauvais style de programmation, mais cela peut-il arrêter quelqu'un qui, de toute façon, ne nous veut pas de bien ?

Pour cela, nous devons créer notre propre fonction fictive init(), dans laquelle nous intercepterons celle qui a été définie LD_PRELOAD et la transmettre à notre compilateur :

#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;
}

On exécute, on vérifie :

$ 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/

Cependant, la mémoire n'est pas le dernier endroit où l'on peut détecter une substitution LD_PRELOAD, il y a aussi /proc/. Commençons par l'évidence /proc/{PID}/environ.

Il existe en réalité une solution universelle pour le undetect. **environ et /proc/self/environLe problème réside dans le comportement "incorrect" de unsetenv(env).

la version correcte

void evil_init()
{
  // D'abord, sauvegardons la valeur actuelle de 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) {
        // Se débarrasser du LD_PRELOAD actuel 
        //unsetenv(env);
        // Au lieu de unset, on va juste mettre notre variable à zéro
        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
$

Mais imaginons que nous ne l'ayons pas trouvé et /proc/self/environ contient des données "problématiques".

Essayons d'abord notre précédente "masquage" :

$ (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 utilise pour ouvrir le fichier le même open(), donc la solution est similaire à ce qui a déjà été fait dans le point 2.1, mais maintenant nous créons un fichier temporaire où nous copions les valeurs de la mémoire réelle sans les lignes contenant 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);
}

Et cette étape est franchie :

$ (LD_PRELOAD=./ld_undetect_proc_environ.so cat /proc/self/environ; echo) | tr " 00" "n" | grep -F LD_PRELOAD
$

Le prochain endroit évident est /proc/self/maps. Il n'est pas utile de s'y attarder. La solution est absolument identique à la précédente : on copie les données du fichier en soustrayant les lignes entre libc.so et ld.so.

2.4. Variante de Chokepoint

Cette solution m'a particulièrement plu par sa simplicité. On compare les adresses des fonctions chargées directement depuis libc, et l'adresse "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;
}

On charge la bibliothèque avec l'interception de "open()" et on vérifie :

$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (appel système - open) [+]
Adresse de la bibliothèque : 0x7fa86893b160
Prochaine adresse : 0x7fa868a26135

Le débogage s'est avéré encore plus simple :

#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. Appels système

Tout semblerait terminé, mais examinons un peu plus. Si nous envoyons l'appel système directement au noyau, cela permettra de contourner tout le processus d'interception. La solution ci-dessous, bien sûr, dépend de l'architecture (x86_64). Essayons de l'implémenter pour détecter l'ouverture 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 (appel système open) [+]

Et ce problème a une solution. Extrait de man‘a :

ptrace est un outil permettant à un processus parent d'observer et de contrôler l'exécution d'un autre processus, de visualiser et de modifier ses données et ses registres. Cette fonction est généralement utilisée pour créer des points d'arrêt lors du débogage et suivre les appels système.

Le processus parent peut initier le traçage en appelant d'abord la fonction fork(2), puis le processus fils résultant peut exécuter PTRACE_TRACEME, suivi de l'exécution de exec(3). D'autre part, le processus parent peut commencer à déboguer un processus existant en utilisant PTRACE_ATTACH.

Lors du traçage, le processus fils s'arrête à chaque fois qu'il reçoit un signal, même si ce signal est ignoré. (L'exception est SIGKILL, qui fonctionne normalement.) Le processus parent sera informé lors de l'appel wait(2), après quoi il peut visualiser et modifier le contenu du processus fils avant son démarrage. Ensuite, le processus parent autorise le fils à continuer, en ignorant parfois le signal envoyé ou en envoyant un autre signal à la place).

Ainsi, la solution consiste à suivre le processus en l'arrêtant avant chaque appel système et, si nécessaire, en redirigeant le flux vers une fonction de piège.

#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, &regs);
                        // 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, &regs);
                    }
                }
                ptrace(PTRACE_SYSCALL, program, 0, 0);
                waitpid(program, &status, 0);
            }
        }
        exit(0);
    }
    else {
        sleep(0);
    }
}

Vérifions :

$ ./detect_syscall
LD_PRELOAD (appel système open) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (appel système open) [-]

+0-0=5

Un grand merci

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, dont les articles, les codes sources et les commentaires ont fait bien plus que moi pour que cette note apparaisse ici.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster