Suche nach LD_PRELOAD

Diese Notiz wurde im Jahr 2014 geschrieben, aber ich wurde gerade wegen Habré unterdrückt und sie erblickte nicht das Licht der Welt. Während des Verbots habe ich es vergessen, aber jetzt habe ich es in den Entwürfen gefunden. Ich habe darüber nachgedacht, es zu löschen, aber vielleicht ist es für jemanden nützlich.

Suche nach LD_PRELOAD

Im Allgemeinen eine kleine Freitags-Administratorlektüre zum Thema Suche nach „included“ LD_PRELOAD.

1. Ein kurzer Exkurs für diejenigen, die sich mit Funktionssubstitution nicht auskennen

Der Rest kann direkt erledigt werden p.2.

Beginnen wir mit einem klassischen Beispiel:

#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 Flags:

$ gcc ./ld_rand.c -o ld_rand

Und wie erwartet erhalten wir 5 Zufallszahlen kleiner als 100:

$ ./ld_rand
53
93
48
57
20

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

Erstellen wir unsere eigene Bibliothek mit unserem eigenen Funktionsprototyp, zum Beispiel:

int rand(){
  return 42;
}

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

Und jetzt ist unsere zufällige Wahl ziemlich vorhersehbar:

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

Dieser Trick sieht noch beeindruckender aus, wenn wir unsere Bibliothek zunächst über exportieren

$ export LD_PRELOAD=$PWD/ld_rand.so

Oder wir machen es zuerst

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

und führen Sie dann das Programm wie gewohnt aus. Wir haben keine einzige Codezeile im Programm selbst geändert, aber sein Verhalten hängt jetzt von einer winzigen Funktion in unserer Bibliothek ab. Darüber hinaus war zum Zeitpunkt des Schreibens des Programms die and existierte gar nicht.

Was hat dazu geführt, dass unser Programm eine Fälschung verwendet hat? and? Gehen wir es Schritt für Schritt an.
Beim Start einer Anwendung werden bestimmte Bibliotheken geladen, die die vom Programm benötigten Funktionen enthalten. Wir können sie mit anzeigen 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)

Diese Liste kann je nach Betriebssystemversion variieren, es muss jedoch eine Datei vorhanden sein libc.so. Es ist diese Bibliothek, die Systemaufrufe und Grundfunktionen bereitstellt, wie z XNUMXh geöffnet, malloc, printf usw. Unsere and gehört auch dazu. Stellen wir Folgendes sicher:

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

Mal sehen, ob sich der Satz an Bibliotheken bei der Verwendung ändert 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 Variable gesetzt ist LD_PRELOAD zwingt uns zum Laden ld_rand.so auch wenn das Programm selbst dies nicht erfordert. Und da unsere Funktion „Rand“ Lädt früher als and aus libc.so, dann regiert sie das Quartier.

Ok, wir haben es geschafft, die native Funktion zu ersetzen, aber wie können wir sicherstellen, dass ihre Funktionalität erhalten bleibt und einige Aktionen hinzugefügt werden? Ändern wir unseren Zufall:

#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 geben wir als „Ergänzung“ nur eine Textzeile aus und erstellen anschließend einen Zeiger auf die ursprüngliche Funktion and. Um die Adresse dieser Funktion zu erhalten, benötigen wir dlsym ist eine Funktion aus der Bibliothek libdldie unsere finden werden and in einem Stapel dynamischer Bibliotheken. Anschließend rufen wir diese Funktion auf und geben ihren Wert zurück. Dementsprechend müssen wir hinzufügen „-ldl“ während der Montage:

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

$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Evil injected code
66
Evil injected code
28
Evil injected code
93
Evil injected code
93
Evil injected code
95

Und unser Programm verwendet „native“ and, nachdem er zuvor einige obszöne Handlungen durchgeführt hatte.

2. Der Schmerz des Suchens

Da wir über eine potenzielle Bedrohung Bescheid wissen, möchten wir diese erkennen Vorspannung Es wurde durchgeführt. Es ist klar, dass der beste Weg zur Erkennung darin besteht, es in den Kernel zu verschieben, aber ich war an den Erkennungsoptionen im Userspace interessiert.

Als nächstes werden Lösungen für die Erkennung und deren Widerlegung paarweise präsentiert.

2.1. Fangen wir einfach an

Wie bereits erwähnt, können Sie mithilfe der Variablen die zu ladende Bibliothek angeben LD_PRELOAD oder indem Sie es in eine Datei schreiben /etc/ld.so.preload. Lassen Sie uns zwei einfache Detektoren erstellen.

Der erste Schritt besteht darin, die festgelegte Umgebungsvariable zu überprüfen:

#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 Schritt besteht darin, zu prüfen, ob die Datei geöffnet ist:

#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 unten zeigt [+] eine erfolgreiche Erkennung an.
Dementsprechend bedeutet [-], dass die Erkennung umgangen wird.

Wie effektiv ist ein solcher Detektor? Schauen wir uns zunächst die Umgebungsvariable an:

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

Ebenso werden wir den Scheck los XNUMXh geöffnet:

#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önnen auch andere Möglichkeiten zum Zugriff auf die Datei verwendet werden, z. B. open64, Zustand usw., aber tatsächlich sind die gleichen 5-10 Codezeilen erforderlich, um sie zu täuschen.

2.2. Lass uns weitermachen

Oben haben wir verwendet getenv() um den Wert zu bekommen LD_PRELOAD, aber es gibt auch einen „niedrigeren“ Weg dorthin ENV-Variablen. Wir werden keine Zwischenfunktionen verwenden, sondern auf das Array verweisen **Umgebung, das eine Kopie der Umgebung speichert:

#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 Anruf nicht abgefangen werden, und unser undetect_getenv Es beeinträchtigt nicht mehr die Identifizierung des Einbruchs.

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

Es scheint, dass dieses Problem gelöst ist? Es fängt noch gerade erst an.

Nach dem Programmstart wird der Wert der Variablen angezeigt LD_PRELOAD Hacker benötigen es nicht mehr im Speicher, das heißt, sie können es lesen und löschen, bevor sie Anweisungen ausführen. Natürlich ist das Bearbeiten eines Arrays im Speicher zumindest ein schlechter Programmierstil, aber wie kann das jemanden davon abhalten, uns sowieso nicht wirklich alles Gute zu wünschen?

Dazu müssen wir unsere eigene Fake-Funktion erstellen drin(), in dem wir das Installierte abfangen LD_PRELOAD und übergeben Sie es an unseren 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;
}

Wir führen aus und prü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 die Erinnerung nicht der letzte Ort, an dem Sie einen Ersatz finden können LD_PRELOAD, Es gibt auch / proc /. Beginnen wir mit dem Offensichtlichen /proc/{PID}/environ.

Tatsächlich gibt es eine universelle Lösung dafür unentdeckt **Umgebung и /proc/self/environ. Das Problem ist das „falsche“ Verhalten unsetenv(env).

Korrekte Möglichkeit

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);
        // Вместо unset просто обнулим нашу переменную
        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 uns vor, wir hätten es nicht gefunden und /proc/self/environ enthält „problematische“ Daten.

Versuchen wir es zunächst mit unserer bisherigen „Verkleidung“:

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

Katze verwendet dasselbe, um die Datei zu öffnen öffnen(), daher ist die Lösung ähnlich wie in Abschnitt 2.1, aber jetzt erstellen wir eine temporäre Datei, in die wir die Werte des wahren Speichers kopieren, ohne Zeilen zu enthalten 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 diese Phase ist geschafft:

$ (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 hat keinen Sinn, darüber nachzudenken. Die Lösung ist absolut identisch mit der vorherigen: Kopieren Sie die Daten aus der Datei abzüglich der Zeilen dazwischen libc.so и ld.so.

2.4. Option von Chokepoint

Diese Lösung gefiel mir besonders wegen ihrer Einfachheit. Wir vergleichen die Adressen von Funktionen, die direkt von geladen werden libc, und „NÄCHSTE“ 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;
}

Laden der Bibliothek mit Abfangen "offen()" und prüfe:

$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc address: 0x7fa86893b160
Next address: 0x7fa868a26135

Die Widerlegung erwies sich als 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

Es scheint, dass das alles ist, aber wir werden trotzdem scheitern. Wenn wir den Systemaufruf direkt an den Kernel leiten, wird der gesamte Abfangprozess umgangen. Die folgende Lösung ist natürlich architekturabhängig (x86_64). Versuchen wir, es zu implementieren, um eine Öffnung zu erkennen 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) [+]

Und dieses Problem hat eine Lösung. Ausschnitt aus Mann'A:

ptrace ist ein Tool, das es einem übergeordneten Prozess ermöglicht, den Fortschritt eines anderen Prozesses zu beobachten und zu steuern sowie dessen Daten und Register anzuzeigen und zu ändern. Typischerweise wird diese Funktion verwendet, um Haltepunkte in einem Debugging-Programm zu erstellen und Systemaufrufe zu überwachen.

Der übergeordnete Prozess kann mit der Ablaufverfolgung beginnen, indem er zunächst fork(2) aufruft, und dann kann der resultierende untergeordnete Prozess PTRACE_TRACEME ausführen, gefolgt (normalerweise) von der Ausführung von exec(3). Andererseits kann ein übergeordneter Prozess mithilfe von PTRACE_ATTACH mit dem Debuggen eines vorhandenen Prozesses beginnen.

Beim Tracing stoppt der untergeordnete Prozess jedes Mal, wenn er ein Signal empfängt, auch wenn das Signal ignoriert wird. (Die Ausnahme ist SIGKILL, das normal funktioniert.) Der übergeordnete Prozess wird darüber durch den Aufruf von wait(2) benachrichtigt, woraufhin er den Inhalt des untergeordneten Prozesses anzeigen und ändern kann, bevor dieser startet. Der übergeordnete Prozess lässt dann zu, dass der untergeordnete Prozess weiterläuft, wobei er in einigen Fällen das an ihn gesendete Signal ignoriert oder stattdessen ein anderes Signal sendet.

Die Lösung besteht also darin, den Prozess zu überwachen, ihn vor jedem Systemaufruf zu stoppen und den Thread bei Bedarf auf eine Hook-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);
    }
}

Wir prü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
Engstelle
ValdikSS
Philippe Teuwen
derhass

, dessen Artikel, Quellen und Kommentare viel mehr als ich dazu beigetragen haben, dass diese Notiz hier erscheint.

Source: habr.com

Kommentar hinzufügen