V iskanju LD_PRELOAD

Ta opomba je bila napisana leta 2014, vendar sem ravno takrat padel pod represijo na Habu in ni bila objavljena. Med časom prepovedi sem jo pozabil, zdaj pa sem jo našel v osnutkih. Razmišljal sem, da bi jo izbrisal, a lahko da bo komu koristila.

V iskanju LD_PRELOAD

Na splošno, kratko petkovo branje za skrbnike na temo iskanja 'vključenega'. LD_PRELOAD.

1. Kratka odstopanja za tiste, ki ne poznajo zamenjave funkcij.

Ostali lahko takoj preidejo na točko 2.

Začnimo s klasičnim primerom:

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

Kompiliramo brez kakršnih koli zastavic:

$ gcc ./ld_rand.c -o ld_rand

In, pričakovano, dobimo 5 naključnih števil manj od 100:

$ ./ld_rand
53
93
48
57
20

Ampak predvidevajmo, da nimamo izvorne kode programa, vendar moramo spremeniti njegovo delovanje.

Ustvarimo svojo knjižnico s svojim prototipom funkcije, na primer:

int rand(){
  return 42;
}

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

In zdaj je naš naključni izbor povsem predvidljiv:

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

Ta trik izgleda še bolj impresivno, če najprej izvozimo našo knjižnico prek

$ export LD_PRELOAD=$PWD/ld_rand.so

ali vnaprej izvedemo

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

in nato zaženemo program v normalnem načinu. Nismo spremenili niti vrstice v kodi samega programa, vendar se njegovo delovanje zdaj zanaša na majhno funkcijo v naši knjižnici. Še več, ob pisanju programa ponarejen rand sploh ni obstajal.

Kaj je torej naš program prisililo, da je uporabil ponarejeni rand? Разберем по шагам.
Ko se aplikacija zažene, se naložijo določene knjižnice, ki vsebujejo funkcije, potrebne programu. Te lahko vidimo z uporabo 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)

Ta seznam se lahko razlikuje glede na različico OS, a nujno mora biti datoteka libc.so. Prav ta knjižnica omogoča sistemske klice in osnovne funkcije, kot so open, malloc, printf itd. Naš rand tudi spada med te. Prepričajmo se o tem:

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

Poglejmo, ali se seznam knjižnic spremeni z uporabo 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)

Izkazuje se, da nameščena spremenljivka LD_PRELOAD prisili, da se naloži naša ld_rand.so čeprav program sam tega ne zahteva. In ker se naša funkcija "rand" nalaga prej kot rand od libc.so, ona prevlada.

V redu, uspelo nam je zamenjati domačo funkcijo, a kako dodati nove funkcionalnosti poleg obstoječih. Modificirajmo naš naključni:

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

Tukaj kot naš 'dodatek' le izpišemo eno vrstico besedila, nato pa ustvarimo kazalec na izvorno funkcijo. rand. Za pridobitev naslova te funkcije bomo potrebovali dlsym — to je funkcija iz knjižnice libdl, ki bo našla našo rand v skladištu dinamičnih knjižnic. Nato bomo poklicali to funkcijo in vrnili njeno vrednost. Zato jo moramo dodati «-ldl» pri sestavljanju:

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

$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Zlobna vbrizgana koda
66
Zlobna vbrizgana koda
28
Zlobna vbrizgana koda
93
Zlobna vbrizgana koda
93
Zlobna vbrizgana koda
95

In naš program uporablja «native» rand, potem ko je opravil nekaj brezsramnih dejanj.

2. Mučenja iskanja

Zavedajoč se potencialne grožnje, želimo odkriti, kaj preload je bilo izvedeno. Očitno je, da je najboljši način za zaznavanje — vstaviti to v jedro, vendar me zanimajo ravno možnosti določanja v uporabniškem prostoru.

Nato bosta sledila rešitvama za odkrivanje in njuno ovrženje.

2.1. Začnimo z enostavnim

Kot je bilo že omenjeno, lahko naloženo knjižnico določimo z okoljsko spremenljivko LD_PRELOAD ali jo navedemo v datoteki /etc/ld.so.preload. Ustvarili bomo dva najpreprostejša detektorja.

Prvi — za preverjanje nastavljene okoljske spremenljivke:

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

Drugi — za preverjanje odpiranja datoteke:

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

Naložimo knjižnice:

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

Tukaj in naprej [+] označuje uspešno odkrivanje.
Zato [-] pomeni obhod zaznavanja.

Kako učinkovit je tako odkrivalnik? Najprej se ukvarjamo z okoljsko spremenljivko:

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

Podobno se znebimo tudi preverjanja 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) [-]

Da, tukaj se lahko uporabijo druge metode dostopa do datoteke, kot so, open64, stat itd., vendar pa v bistvu za njihovo zavajanje potrebujemo enakih 5-10 vrstic kode.

2.2. Gremo naprej

Zgornji del smo uporabili getenv() za pridobitev vrednosti LD_PRELOAD, vendar obstaja tudi bolj »nizkotovno« sredstvo za dostop do ENV-spremenljivk. Ne bomo uporabljali vmesnih funkcij, temveč se bomo obrnili neposredno na matriko **environ, v kateri je shranjena kopija okolja:

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

Ker tukaj podatke beremo neposredno iz pomnilnika, ni mogoče izvesti takega klica, in naš undetect_getenv ne ovira več določanja vdrivanja.

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

Zdi se, da je s tem problem rešen? Vse šele začne.

Ko se program zažene, vrednost spremenljivke LD_PRELOAD v pomnilniku več ni potrebna hekerjem, torej jo lahko štejemo kot odstranjeno, preden izvedemo kakršne koli ukaze. Seveda, spreminjanje masiva v pomnilniku je slaba praksa programiranja, vendar ali to lahko ustavi nekoga, ki nam itak ne želi dobro?

Zato moramo ustvariti našo lažno funkcijo init(), v kateri bomo prestregli nastavljeno LD_PRELOAD in jo posredovali našemu linkerju:

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

Izvedemo, preverimo:

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

Vendar pa pomnilnik ni zadnje mesto, kjer lahko odkrijemo spremembo LD_PRELOAD, še vedno obstaja /proc/. Začnimo s očitnim /proc/{PID}/environ.

Dejansko obstaja univerzalna rešitev za undetect'a **environ in /proc/self/environ. Težava je v "nepravilnem" vedenju unsetenv(env).

pravilna različica

void evil_init()
{
  // Najprej shranimo trenutno vrednost 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) {
        // Znebimo se trenutnega LD_PRELOAD 
        //unsetenv(env);
        // Namesto unset zgolj nastavimo našo spremenljivko na nič
        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
$

Ampak si predstavljajmo, da ga nismo našli in /proc/self/environ vsebuje "problematične" podatke.

Najprej poskusimo z našim prejšnjim "maskiranjem":

$ (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 uporablja za odpiranje datoteke še vedno tisto isto open(), zato je rešitev podobna tisti v točki 2.1, vendar zdaj ustvarimo začasno datoteko, kamor kopiramo vrednosti iz resničnega pomnilnika brez vrstic, ki vsebujejo 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);
}

In ta korak je zaključen:

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

Naslednje očitno mesto je /proc/self/maps. Ni smisla zadrževati se na njem. Rešitev je popolnoma identična prejšnji: kopiramo podatke iz datoteke brez vrstic med libc.so in ld.so.

2.4. Različica od Chokepoint

Ta rešitev mi je še posebej všeč zaradi svoje preprostosti. Primerjamo naslove funkcij, ki se neposredno nalagajo iz libc, in "NEXT" naslove.

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

Naložimo knjižnico s prestrezanjem "open()" in preverimo:

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

Odziv je bil še enostavnejši:

#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. Sistemski klici

Zdi se, da je to vse, a še malo raziskujmo. Če usmerimo sistemski klic neposredno v jedro, bomo tako obšli celoten proces prestrezanja. Spodaj predlagano rešitev je seveda odvisna od arhitekture (x86_64). Poskusimo jo realizirati za zaznavanje odpiralnih 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) [+]

In ta naloga ima rešitev. Odlomek iz man‘a:

ptrace — je orodje, ki omogoča enemu procesu, da opazuje in nadzira potek drugega procesa, pregleda in spremeni njegove podatke in registre. Ta funkcija se običajno uporablja za ustvarjanje vsadkov v programu pri odpravljanju napak in sledenju sistemskim klicem.

Starševski proces lahko začne sledenje, tako da najprej pokliče funkcijo fork(2), nato pa lahko pridobljeni otroški proces izvede PTRACE_TRACEME, za katerim (običajno) sledi izvrševanje exec(3). Po drugi strani lahko starševski proces začne odpravljanje obstoječega procesa z uporabo PTRACE_ATTACH.

Pri sledenju se otroški proces ustavi vsakič, ko prejme signal, četudi je ta signal ignoriran. (Izjema je SIGKILL, ki deluje običajno.) Starševski proces o tem obvesti klic wait(2), po katerem lahko pregleda in spremeni vsebino otroškega procesa pred njegovim zagonem. Nato starševski proces dovoli otroku, da nadaljuje delo, v nekaterih primerih ignorira poslan signal ali namesto tega pošlje drug signal).

Torej, rešitev je v tem, da spremljamo proces, ga ustavimo pred vsakim sistemskim klicem in, če je potrebno, preusmerimo tok v funkcijo prečkovanja.

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

Preverimo:

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

+0-0=5

Najlepša hvala

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, katerih članki, izvorne kode in komentarji so naredili precej več, kot sem jaz, da se ta opomba tukaj pojavi.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster