Otsides LD_PRELOAD

See märge kirjutati 2014. aastal, kuid ma sattusin just repressioonide alla ja see ei näinud kunagi ilmavalgust. Keelamisaja jooksul unustasin selle ja nüüd leidsin selle mustandite seast. Alguses mõtlesin kustutada, aga äkki läheb kellelegi vaja.

Otsides LD_PRELOAD

Ühesõnaga, väike reedeadmini lugemine teemal „kapitaliseeritud“ otsimine. LD_PRELOAD.

1. Väike kõrvalepõige neile, kes ei tunne funktsioonide asendamisega seotud aspekte.

Teised võivad kohe minna p.2.

Alustame klassikalisest näitest:

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

Kompileerime ilma igasuguste lipikuteta:

$ gcc ./ld_rand.c -o ld_rand

Ja nagu oodata, saame 5 juhuslikku numbrit, mis on väiksemad kui 100:

$ ./ld_rand
53
93
48
57
20

Aga kujutame ette, et meil pole programmi allika koodi ja käitumist on vaja muuta.

Loome oma teegi koos oma funktsiooni prototüübiga, näiteks:

int rand(){
  return 42;
}

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

Ja nüüd on meie juhuslik valik täiesti ennustatav:

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

See trikk näeb välja veelgi muljetavaldavam, kui me kõigepealt ekspordime oma teegi läbi

$ export LD_PRELOAD=$PWD/ld_rand.so

või teeme seda eelnevalt

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

ja seejärel käivitame programmi tavarežiimis. Me ei ole koodis ühtegi rida muutnud, kuid selle käitumine sõltub nüüd meie raamatukogus olevast pisikesest funktsioonist. Veelgi enam, programm kirjutamise ajal ei eksisteerinud vale rand isegi.

Mis pani meie programmi kasutama vale rand? Разберем по шагам.
Kui rakendus käivitatakse, laaditakse teatud raamatukogud, mis sisaldavad programmile vajalikke funktsioone. Saame neid vaadata, kasutades 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)

See loend võib erineda sõltuvalt OS-i versioonist, kuid seal peab kindlasti olema fail libc.so. Just see raamatukogu tagab süsteemikutsed ja põhifunktsioonid, nagu open, malloc, printf jne. Meie rand on samuti nende seas. Veendume selles:

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

Vaadake, kas raamatukogude komplekt muutub, kui kasutame 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)

Selgub, et seatud muutuja LD_PRELOAD sunab meie laadima ld_rand.so isegi kui programm ei nõua seda. Ja kuna meie funktsioon «rand» laaditakse enne rand alates libc.so, siis tema ka valitseb.

Jah, meil õnnestus asendada pärandfunktsioon, aga kuidas teha nii, et selle funktsionaalsus säiliks ja lisataks teatud toiminguid. Muudame meie juhuslikkust:

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

Siin on meie "lisandina" lihtsalt ühe teksti rea printimine, pärast mida loome viite algsesse funktsiooni. rand. Selle funktsiooni aadressi saamiseks vajame dlsym — see on funktsioon raamatukogust libdl, mis leiab meie rand dinamiiliste raamatukogude virnast. Pärast seda kutsume selle funktsiooni välja ja tagastame selle väärtuse. Seega peame lisama "-ldl" koostamisel:

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

$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Kurja kood süstitud
66
Kurja kood süstitud
28
Kurja kood süstitud
93
Kurja kood süstitud
93
Kurja kood süstitud
95

Ja meie programm kasutab "pärand" rand, enne teatud tarbetute toimingute sooritamist.

2. Otsimise mured

Teades potentsiaalsetest ohtudest, soovime tuvastada, et preload oli teostatud. Loomulikult on parim tuvastamismeetod selle südamikku peitmine, kuid mind huvitavad just variandid selle tuvastamiseks kasutajaruumi tasandil.

Edasi tulevad paarid lahendustest tuvastamiseks ja nende ümberlükkamiseks.

2.1. Alustame lihtsast

Nagu mainitud, saab laaditava teegi määrata muutuja abil LD_PRELOAD või kirjutades selle faili /etc/ld.so.preload. Loome kaks lihtsamat detektorit.

Esimene — keskkonnamuutuja kontrollimiseks:

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

Teine — faili avamise kontrollimiseks:

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

Laadime teegid:

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

Siin ja edaspidi [+] tähendab eduka tuvastamise näitaja.
Vastavalt, [-] tähendab tuvastamise vältimist.

Kuidas tõhus on selline tuvastaja? Alustame keskkonnamuutujast:

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

Sarnasel viisil vabaneme ka kontrollist 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) [-]

Jah, siin võivad olla kasutusel muud faili juurde pääsemise meetodid, nagu open64, stat jne, kuid peamine, et nende petmiseks on vaja samu 5-10 koodi rida.

2.2. Liigume edasi

Ülalkasutasime getenv() väärtuse saamiseks LD_PRELOAD, kuid on ka madalama taseme viis sinna jõudmiseks. ENV-muutujad. Ära kasutame vahefunktsioone, vaid pöördume massiivi poole **keskkond, kus säilitatakse koopia keskkonnast:

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

Kuna me loeme andmeid otse mälu, siis sellist kutsumist ei saa peatada ja meie undetect_getenv ei takista enam sissetungi määramist.

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

Tundub, et probleem on lahendatud? Kõik alles algab.

Pärast programmi käivitamist muutub muutuja väärtus LD_PRELOAD mälu ei huvita enam häkkerit, seega on võimalik see lugeda ja eemaldada enne, kui muid juhiseid täidetakse. Loomulikult on mälu massiivi muutmine vähemalt halb programmeerimistiil, kuid kas see võib peatada kedagi, kes ei soovi meiega head?

Selle jaoks peame looma oma vale funktsiooni init(), kus peame kinni seadistatud LD_PRELOAD ja edastama selle meie sidujale:

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

Täitke, kontrollige:

$ 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 (**keskkond) [-]

2.3. /proc/self/

Kuid mälu ei ole viimane koht, kus võib avastada petmist LD_PRELOAD, on veel /proc/. Alustame ilmsest /proc/{PID}/environ.

Tegelikult on olemas universaalne lahendus undetect’ale **keskkond ja /proc/self/environ. Probleem seisneb "vale" käitumises unsetenv(env).

õige variant

void evil_init()
{
  // Esiteks salvestame praeguse LD_PRELOAD väärtuse
  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) {
        // Vabastame praeguse LD_PRELOAD 
        //unsetenv(env);
        // Selle asemel lihtsalt nullime meie muutuja
        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
$

Kuid kujuta ette, et me ei leidnud seda ja /proc/self/environ sisaldab "probleemseid" andmeid.

Esiteks proovime meie eelmise "maskeerimisega":

$ (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 kasutab faili avamiseks ikka sama open(), seega on lahendus sarnane sellele, mida juba tehti p.2.1, kuid nüüd loome ajutise faili, kuhu kopeerime tõelise mälu väärtused ilma ridadeta, mis sisaldavad 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);
}

Ja see etapp on läbitud:

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

Järgmine ilmselge koht on /proc/self/maps. Sellel peatuda ei ole mõtet. Lahendus on täiesti sama nagu eelmine: kopeerime andmed failist, väljaarvatud read vahelt libc.so ja ld.so.

2.4. Chokepointi variant

See lahendus meeldis mulle eriti oma lihtsuse tõttu. Võrdleme otse laaditavate funktsioonide aadresse libc, ja "NEXT"-aadresse.

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

Laadime sisse kattega teegi "open()" ja kontrollime:

$ export LD_PRELOAD=$PWD/ld_undetect_open.so
$ ./detect_chokepoint
LD_PRELOAD (süsteemikõne - open) [+]
Libc aadress: 0x7fa86893b160
Järgmine aadress: 0x7fa868a26135

Lähenemine osutus veelgi lihtsamaks:

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

Kujutame ette, et sellega kõik ei piirdu, vaid proovime veel. Kui suuname süsteemikõne otse tuumale, siis see võimaldab üle minna kogu katteprotsessist. Allolev lahendus on loomulikult arhitektuurist sõltuv (x86_64). Proovime ära teha avamise avastamiseks 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) [+]

Ja see ülesanne on lahendatav. Lõik man‘st:

ptrace on tööriist, mis võimaldab vanemprotsessil jälgida ja juhtida teise protsessi käiku, samuti vaadata ja muuta selle andmeid ja registre. Seda funktsiooni kasutatakse tavaliselt silumisprogrammis katkestuspunktide seadmiseks ja süsteemikutsungite jälgimiseks.

Vanemprotsess saab jälgimise alustada, helistades esmalt funktsioonile fork(2), pärast mida saab loodud lapsprotsess käivitada PTRACE_TRACEME, millele tavaliselt järgneb exec(3) täitmine. Teisest küljest saab vanemprotsess alustada olemasoleva protsessi silumist PTRACE_ATTACH abil.

Jälgimise käigus peatatakse lapsprotsess iga kord, kui ta saab signaali, isegi kui see signaal ignoreeritakse. (Erandiks on SIGKILL, mis töötab tavapäraselt.) Vanemprotsess saab sellest teavituse, kui kutsub esile wait(2), pärast mida saab ta vaadata ja muuta lapsprotsessi sisu enne selle käivitamist. Pärast seda annab vanemprotsess lapsele loa jätkata, mõnel juhul ignoreerides saadetud signaali või saates selle asemel teise signaali.

Тем самым, решение заключается в том, чтобы отслеживать процесс, останавливая его перед каждым системным вызовом и, при необходимости, перенаправлять поток в функцию ловушки.

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

Kontrollin:

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

+0-0=5

Огромное спасибо

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, чьи статьи, исходники и комментарии сделали намного больше, чем я, для того чтобы эта заметка появилась здесь.

Allikas: habr.com

Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster