LD_PRELOAD bila

Ohar hau 2014an idatzi zen, baina Habréren aurkako errepresioa jasan berri nuen eta ez zuen argia ikusi. Debekuan ahaztu egin zait, baina orain zirriborroetan aurkitu dut. Ezabatzea pentsatu nuen, baina agian norbaitentzat baliagarria izango da.

LD_PRELOAD bila

Oro har, ostiraleko administratzaileen irakurketa txiki bat "barne" bilaketaren gaiari buruz LD_PRELOAD.

1. Funtzioen ordezkapena ezagutzen ez dutenentzat digresio labur bat

Gainerakoak zuzenean joan daitezke 2. or.

Has gaitezen adibide klasiko batekin:

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

Banderarik gabe konpilatzen dugu:

$ gcc ./ld_rand.c -o ld_rand

Eta, espero bezala, 5 baino ausazko 100 zenbaki lortuko ditugu:

$ ./ld_rand
53
93
48
57
20

Baina pentsa dezagun ez dugula programaren iturburu-kodea, baina portaera aldatu behar dugula.

Sor dezagun gure liburutegia gure funtzio-prototipoarekin, adibidez:

int rand(){
  return 42;
}

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

Eta orain gure ausazko aukera nahiko aurreikusgarria da:

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

Trikimailu honek are ikusgarriagoa dirudi lehendabizi gure liburutegia bidez esportatzen badugu

$ export LD_PRELOAD=$PWD/ld_rand.so

edo lehenengo egingo dugu

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

eta gero exekutatu programa ohi bezala. Ez dugu kode lerro bakar bat ere aldatu programan bertan, baina bere portaera orain gure liburutegiko funtzio txiki baten araberakoa da. Gainera, egitaraua idazteko unean, rand ez zen existitzen ere.

Zerk eragin zuen gure programa faltsu bat erabiltzea rand? Eman dezagun urratsez urrats.
Aplikazio bat abiarazten denean, programak behar dituen funtzioak dituzten liburutegi batzuk kargatzen dira. Erabiliz ikus ditzakegu 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)

Zerrenda hau alda daiteke OS bertsioaren arabera, baina fitxategi bat egon behar du bertan libc.so. Liburutegi hau sistema-deiak eta oinarrizko funtzioak eskaintzen dituena da ireki, mallok, printf eta abar Gure rand horien artean dago ere. Ziurta dezagun hau:

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

Ea erabiltzen denean liburutegien multzoa aldatzen den 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)

Aldagaia ezarrita dago LD_PRELOAD kargatzera behartzen gaitu ld_rand.so nahiz eta programak berak behar ez duen arren. Eta gure funtzioaz geroztik "rand" kargak baino lehenago rand tik libc.so, orduan berak gobernatzen du.

Ados, jatorrizko funtzioa ordezkatzea lortu dugu, baina nola ziurtatu bere funtzionaltasuna mantentzen dela eta ekintza batzuk gehitzen direla. Alda dezagun gure ausazkoa:

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

Hemen, gure "gehigarri" gisa, testu-lerro bakarra inprimatzen dugu, eta ondoren jatorrizko funtziorako erakuslea sortzen dugu rand. Funtzio honen helbidea lortzeko behar dugu dlsym liburutegiko funtzio bat da libdlgurea aurkituko duena rand liburutegi dinamikoen pila batean. Horren ondoren, funtzio honi deituko diogu eta bere balioa itzuliko diogu. Horren arabera, gehitu beharko dugu "-ldl" muntaian:

$ 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

Eta gure programak "natiboa" erabiltzen du rand, aurretik ekintza lizun batzuk eginda.

2. Bilatzearen mina

Balizko mehatxu bat ezagututa, hori detektatu nahi dugu aurrez kargatu Egin zen. Argi dago detektatzeko modurik onena nukleoan sartzea dela, baina erabiltzaile-espazioko detekzio-aukerak interesatzen zitzaizkidan.

Jarraian, detektatzeko eta haien ezeztapenerako irtenbideak binaka etorriko dira.

2.1. Has gaitezen sinplea

Lehen esan bezala, aldagaia erabiliz kargatu beharreko liburutegia zehaztu dezakezu LD_PRELOAD edo fitxategi batean idatziz /etc/ld.so.preload. Sor ditzagun bi detektagailu sinple.

Lehenengoa ezarri ingurune-aldagaia egiaztatzea da:

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

Bigarrena fitxategia irekita dagoen egiaztatzea da:

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

Karga ditzagun liburutegiak:

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

Hemen eta behean, [+]-k ondo detektatu izana adierazten du.
Horren arabera, [-]-k detekzioa saihestea esan nahi du.

Zein eraginkorra da halako detektagailu bat? Ikus dezagun lehenik ingurune-aldagaiari:

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

Era berean, txekea kentzen dugu ireki:

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

Bai, fitxategira sartzeko beste modu batzuk erabil daitezke hemen, adibidez, irekia64, stat etab., baina, egia esan, 5-10 kode lerro berdinak behar dira haiek engainatzeko.

2.2. Goazen aurrera

Goian erabili genuen getenv() balioa lortzeko LD_PRELOAD, baina iristeko modu "maila baxuago" bat ere badago ENV-aldagaiak. Ez ditugu tarteko funtzioak erabiliko, array-ari erreferentzia egingo diogu baizik **inguru, ingurunearen kopia bat gordetzen duena:

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

Hemen memoriatik zuzenean datuak irakurtzen ari garenez, dei hori ezin da atzeman, eta gure undetect_getenv jada ez du oztopatzen intrusioa identifikatzeko.

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

Arazo hau konponduta dagoela dirudi? Oraindik hasi besterik ez dago.

Programa hasi ondoren, aldagaiaren balioa LD_PRELOAD hackerrek ez dute gehiago behar memorian, hau da, irakurri eta ezabatu dezakete edozein argibide exekutatu aurretik. Jakina, memorian array bat editatzea, gutxienez, programazio estilo txarra da, baina nola gelditu daiteke horrek benetan ongi opa ez gaituen norbait hala ere?

Horretarako gure funtzio faltsua sortu behar dugu hasi(), bertan instalatutakoa atzematen dugu LD_PRELOAD eta pasa ezazu gure estekatzaileari:

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

Exekutatu eta egiaztatzen dugu:

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

Hala ere, memoria ez da ordezkapen bat aurki dezakezun azken lekua LD_PRELOAD, badago ere /proc/. Has gaitezen agerikotik /proc/{PID}/environ.

Izan ere, irtenbide unibertsala dago detektatu gabe **inguru и /proc/self/environ. Arazoa jokabide "okerra" da unsetenv(env).

aukera zuzena

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
$

Baina pentsa dezagun ez dugula aurkitu eta /proc/self/environ datu "problematikoak" ditu.

Lehenik eta behin, saiatu gaitezen gure aurreko "mozorroarekin":

$ (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 gauza bera erabiltzen du fitxategia irekitzeko ireki (), beraz, irtenbidea 2.1 atalean lehendik egindakoaren antzekoa da, baina orain aldi baterako fitxategi bat sortzen dugu, non benetako memoriaren balioak kopiatzen ditugun lerrorik gabe. 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);
}

Eta etapa hau gainditu da:

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

Hurrengo leku agerikoa da /proc/self/maps. Ez du zentzurik horretan aritzeak. Irtenbidea aurrekoaren guztiz berdina da: kopiatu fitxategiko datuak kenduta tarteko lerroak libc.so и ld.beraz.

2.4. Chokepoint-en aukera

Soluzio hau bereziki gustatu zait bere sinpletasunagatik. Zuzenean kargatutako funtzioen helbideak alderatzen ditugu libc, eta "HURRENGO" helbideak.

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

Liburutegia atzematearekin kargatzen "ireki()" eta egiaztatu:

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

Errefusa are sinpleagoa izan zen:

#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. Syscall-ak

Badirudi hau dena dela, baina hala ere kolokan geratuko gara. Sistema-deia zuzenean nukleora zuzentzen badugu, honek atzemate-prozesu osoa saihestuko du. Beheko irtenbidea, noski, arkitekturaren menpekoa da (x86_64). Saia gaitezen inplementatzen irekiera bat detektatzeko ld.so.aurrekarga.

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

Eta arazo honek badu irtenbidea. -ren zatia man'A:

ptrace prozesu nagusi bati beste prozesu baten aurrerapena behatu eta kontrolatzeko, bere datuak eta erregistroak ikusi eta aldatzeko aukera ematen dion tresna da. Normalean funtzio hau arazketa-programa batean eten-puntuak sortzeko eta sistema-deiak kontrolatzeko erabiltzen da.

Prozesu nagusia trazatzen has daiteke lehenik fork(2) deituz, eta, ondoren, ondoriozko prozesu seme-alabak PTRACE_TRACEME exekutatu dezake, eta ondoren (normalean) exec(3) exekuta dezake. Bestalde, prozesu nagusi bat lehendik dagoen prozesu bat arazketan has daiteke PTRACE_ATTACH erabiliz.

Trazatzean, haurraren prozesua gelditzen da seinale bat jasotzen duen bakoitzean, nahiz eta seinalea ez ikusi. (Salbuespena SIGKILL da, normalean funtzionatzen duena.) Prozesu nagusiari wait(2) deituz jakinaraziko zaio, eta ondoren, hasi aurretik, prozesu seme-alaben edukia ikusi eta aldatu ahal izango du. Guraso-prozesuak umeari korrika egiten jarraitzeko aukera ematen dio, kasu batzuetan bidalitako seinalea alde batera utziz edo beste seinale bat bidaliz).

Horrela, irtenbidea prozesua monitorizatzea da, sistema dei bakoitzaren aurretik gelditu eta, behar izanez gero, haria kako funtzio batera birbideratzea.

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

Egiaztatzen dugu:

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

+0-0=5

Mila esker

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, zeinaren artikulu, iturri eta iruzkinek nik baino askoz gehiago egin zuten ohar hau hemen agertzeko.

Iturria: www.habr.com

Gehitu iruzkin berria