Mencari LD_PRELOAD

Nota ini ditulis pada tahun 2014, tetapi saya baru sahaja mengalami penindasan terhadap Habré dan ia tidak dapat dilihat. Semasa larangan itu saya terlupa mengenainya, tetapi sekarang saya menemuinya dalam draf. Saya terfikir untuk memadamkannya, tetapi mungkin ia berguna kepada seseorang.

Mencari LD_PRELOAD

Secara umum, sedikit hari Jumaat admin membaca tentang topik mencari "disertakan" LD_PRELAD.

1. Penyimpangan pendek untuk mereka yang tidak biasa dengan penggantian fungsi

Selebihnya boleh terus ke hlm.2.

Mari kita mulakan dengan contoh klasik:

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

Kami menyusun tanpa sebarang bendera:

$ gcc ./ld_rand.c -o ld_rand

Dan, seperti yang dijangkakan, kami mendapat 5 nombor rawak kurang daripada 100:

$ ./ld_rand
53
93
48
57
20

Tetapi mari kita bayangkan bahawa kita tidak mempunyai kod sumber program, tetapi kita perlu mengubah tingkah laku.

Mari kita cipta perpustakaan kita sendiri dengan prototaip fungsi kita sendiri, sebagai contoh:

int rand(){
  return 42;
}

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

Dan kini pilihan rawak kami agak boleh diramalkan:

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

Silap mata ini kelihatan lebih mengagumkan jika kami mengeksport perpustakaan kami terlebih dahulu melalui

$ export LD_PRELOAD=$PWD/ld_rand.so

atau kita buat dulu

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

dan kemudian jalankan program seperti biasa. Kami tidak mengubah satu baris kod dalam program itu sendiri, tetapi kelakuannya kini bergantung pada fungsi kecil dalam perpustakaan kami. Lebih-lebih lagi, pada masa penulisan program, the rand tidak wujud pun.

Apa yang menjadikan program kami menggunakan palsu rand? Mari kita ambil langkah demi langkah.
Apabila aplikasi bermula, perpustakaan tertentu dimuatkan yang mengandungi fungsi yang diperlukan oleh program. Kita boleh melihatnya menggunakan 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)

Senarai ini mungkin berbeza-beza bergantung pada versi OS, tetapi mesti ada fail di sana libc.so. Perpustakaan inilah yang menyediakan panggilan sistem dan fungsi asas seperti membuka, malloc, Printf dll. Kami rand juga antara mereka. Mari pastikan ini:

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

Mari lihat jika set perpustakaan berubah apabila digunakan LD_PRELAD

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

Ternyata pembolehubah ditetapkan LD_PRELAD memaksa kita untuk memuatkan ld_rand.so walaupun pada hakikatnya program itu sendiri tidak memerlukannya. Dan sejak fungsi kami "rand" memuatkan lebih awal daripada rand daripada libc.so, kemudian dia memerintah bertengger.

Ok, kami berjaya menggantikan fungsi asli, tetapi bagaimana kami boleh memastikan fungsinya dipelihara dan beberapa tindakan ditambah. Mari ubah suai rawak kami:

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

Di sini, sebagai "penambahan" kami, kami hanya mencetak satu baris teks, selepas itu kami membuat penunjuk ke fungsi asal rand. Untuk mendapatkan alamat fungsi ini kita perlukan dlsym adalah fungsi daripada perpustakaan libdlyang akan menemui kita rand dalam timbunan perpustakaan dinamik. Selepas itu kami akan memanggil fungsi ini dan mengembalikan nilainya. Sehubungan itu, kita perlu menambah "-ldl" semasa perhimpunan:

$ 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

Dan program kami menggunakan "asli" rand, setelah melakukan beberapa tindakan lucah sebelum ini.

2. Keperitan mencari

Mengetahui tentang potensi ancaman, kami ingin mengesannya Pramuat Ia dilakukan. Sudah jelas bahawa cara terbaik untuk mengesan adalah dengan menolaknya ke dalam kernel, tetapi saya berminat dengan pilihan pengesanan dalam ruang pengguna.

Seterusnya, penyelesaian untuk pengesanan dan penyangkalannya akan datang secara berpasangan.

2.1. Mari kita mulakan dengan mudah

Seperti yang dinyatakan sebelum ini, anda boleh menentukan perpustakaan untuk dimuatkan menggunakan pembolehubah LD_PRELAD atau dengan menulisnya dalam fail /etc/ld.so.pramuat. Mari buat dua pengesan mudah.

Yang pertama ialah menyemak pembolehubah persekitaran yang ditetapkan:

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

Yang kedua adalah untuk menyemak sama ada fail dibuka:

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

Mari muatkan perpustakaan:

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

Di sini dan di bawah, [+] menunjukkan pengesanan yang berjaya.
Sehubungan itu, [-] bermaksud memintas pengesanan.

Sejauh manakah pengesan sedemikian berkesan? Mari kita lihat pembolehubah persekitaran terlebih dahulu:

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

Begitu juga, kita menyingkirkan cek membuka:

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

Ya, cara lain untuk mengakses fail boleh digunakan di sini, seperti, open64, stat dan lain-lain, tetapi, sebenarnya, 5-10 baris kod yang sama diperlukan untuk menipu mereka.

2.2. Jom teruskan

Di atas kita gunakan getenv() untuk mendapatkan nilai LD_PRELAD, tetapi terdapat juga cara yang lebih "peringkat rendah" untuk sampai ke ENV-pembolehubah. Kami tidak akan menggunakan fungsi perantaraan, tetapi akan merujuk kepada tatasusunan **persekitaran, yang menyimpan salinan persekitaran:

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

Oleh kerana di sini kita membaca data terus dari ingatan, panggilan sedemikian tidak boleh dipintas, dan panggilan kita undetect_getenv ia tidak lagi mengganggu mengenal pasti pencerobohan.

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

Nampaknya masalah ini telah selesai? Ia masih baru bermula.

Selepas program dimulakan, nilai pembolehubah LD_PRELAD penggodam tidak lagi memerlukannya dalam ingatan, iaitu, mereka boleh membaca dan memadamnya sebelum melaksanakan sebarang arahan. Sudah tentu, menyunting tatasusunan dalam ingatan adalah, sekurang-kurangnya, gaya pengaturcaraan yang buruk, tetapi bagaimana ini boleh menghalang seseorang yang tidak mendoakan kita dengan baik?

Untuk melakukan ini, kita perlu mencipta fungsi palsu kita sendiri di dalamnya(), di mana kami memintas yang dipasang LD_PRELAD dan serahkan kepada pemaut kami:

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

Kami melaksanakan dan menyemak:

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

Walau bagaimanapun, ingatan bukanlah tempat terakhir di mana anda boleh mencari pengganti LD_PRELAD, terdapat juga /proc/. Mari kita mulakan dengan yang jelas /proc/{PID}/environ.

Malah, terdapat penyelesaian universal untuk tidak dapat dikesan **persekitaran и /proc/self/environ. Masalahnya ialah tingkah laku "salah". unsetenv(env).

pilihan yang betul

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
$

Tetapi mari kita bayangkan bahawa kita tidak menemuinya dan /proc/self/environ mengandungi data "bermasalah".

Mula-mula mari cuba dengan "penyamaran" kami sebelum ini:

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

kucing menggunakan perkara yang sama untuk membuka fail buka(), jadi penyelesaiannya adalah serupa dengan apa yang telah dilakukan dalam bahagian 2.1, tetapi kini kami membuat fail sementara di mana kami menyalin nilai memori sebenar tanpa baris yang mengandungi LD_PRELAD.

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

Dan peringkat ini telah berlalu:

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

Tempat yang jelas seterusnya ialah /proc/self/maps. Tidak ada gunanya memikirkannya. Penyelesaiannya benar-benar sama dengan yang sebelumnya: salin data dari fail tolak baris antara libc.so и ld.so.

2.4. Pilihan daripada Chokepoint

Saya sangat menyukai penyelesaian ini kerana kesederhanaannya. Kami membandingkan alamat fungsi yang dimuatkan terus dari libc, dan alamat "SETERUSNYA".

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

Memuatkan perpustakaan dengan pemintasan "buka()" dan semak:

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

Penolakan itu ternyata lebih mudah:

#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

Nampaknya ini sahaja, tetapi kita masih akan menggelepar. Jika kita mengarahkan panggilan sistem terus ke kernel, ini akan memintas keseluruhan proses pemintasan. Penyelesaian di bawah, sudah tentu, bergantung kepada seni bina (x86_64). Mari cuba laksanakannya untuk mengesan pembukaan ld.so.pramuat.

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

Dan masalah ini ada penyelesaiannya. Petikan daripada lelaki'A:

ptrace ialah alat yang membolehkan proses induk memerhati dan mengawal kemajuan proses lain, melihat dan menukar data dan daftarnya. Biasanya fungsi ini digunakan untuk mencipta titik putus dalam program penyahpepijatan dan memantau panggilan sistem.

Proses induk boleh mula mengesan dengan terlebih dahulu memanggil fork(2), dan kemudian proses anak yang terhasil boleh melaksanakan PTRACE_TRACEME, diikuti (biasanya) dengan melaksanakan exec(3). Sebaliknya, proses induk boleh mula menyahpepijat proses sedia ada menggunakan PTRACE_ATTACH.

Apabila mengesan, proses kanak-kanak berhenti setiap kali ia menerima isyarat, walaupun isyarat itu diabaikan. (Pengecualian ialah SIGKILL, yang berfungsi seperti biasa.) Proses induk akan dimaklumkan tentang perkara ini dengan memanggil tunggu(2), selepas itu ia boleh melihat dan mengubah suai kandungan proses anak sebelum ia bermula. Proses ibu bapa kemudian membenarkan kanak-kanak itu untuk terus berlari, dalam beberapa kes mengabaikan isyarat yang dihantar kepadanya atau menghantar isyarat lain sebaliknya).

Oleh itu, penyelesaiannya adalah untuk memantau proses, menghentikannya sebelum setiap panggilan sistem dan, jika perlu, ubah hala benang ke fungsi cangkuk.

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

Kami menyemak:

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

+0-0=5

Terima kasih banyak-banyak

Charles Hubain
Titik tercekik
ValdikSS
Philippe Teuwen
derhass

, yang artikel, sumber dan ulasannya melakukan lebih daripada yang saya lakukan untuk membuat nota ini muncul di sini.

Sumber: www.habr.com

Tambah komen