LD_PRELOAD axtarışında

Bu yazı 2014 yılında yazılmıştı, ancak ben tam o sırada Habr'da baskılara maruz kaldım ve bu yazı görünmedi. Yasaklı olduğum süre boyunca bunu unuttum, şimdi ise taslaklarımda buldum. Silmeyi düşünmüştüm, ama belki birine yarar.

LD_PRELOAD axtarışında

Kısacası, "açık" olanı bulmak üzerine küçük bir Cuma yönetici okuması. LD_PRELOAD.

1. Fonksiyon ikamesi ile tanışmayanlar için küçük bir ara.

Diğerleri hemen madde 2'ye.

geçebilir.

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

Klasik bir örnekle başlayalım:

Hiçbir bayrak olmadan derleyelim:

$ gcc ./ld_rand.c -o ld_rand

Ve, beklenildiği gibi, 100'den küçük 5 rastgele sayı alıyoruz:

$ ./ld_rand 53 93 48 57 20

Ama farz edelim ki programın kaynak koduna sahip değiliz ve davranışını değiştirmemiz gerekiyor.

Kendi fonksiyon prototipimizle bir kütüphane oluşturacağız, örneğin:

int rand(){
  return 42;
}

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

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

Ve şimdi rastgele seçimimiz oldukça öngörülebilir hale geldi:

Bu numara daha da etkileyici görünüyor; eğer önce kütüphanemizi aşağıdaki gibi dışa aktarırsak:

$ export LD_PRELOAD=$PWD/ld_rand.so

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

ya da önce aşağıdakini uygularsak ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış bir rand

bile yoktu. ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış bir? Разберем по шагам.
O halde, programımızı sahte 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)

başlatıldığında, belirli kütüphaneler yüklenir ve bu kütüphaneler programe gerekli olan fonksiyonları içerir. Bunları görmek için kullanabiliriz. Bu liste OS sürümüne bağlı olarak farklılık gösterebilir, ancak kesinlikle bir dosya olmalıdırlibc.so. Bu kütüphane sistem çağrılarını ve temel işlevleri, gibi işlemleri sağlar., malloc, printf vb. Bizim ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış bir da onların arasında. Bunun doğru olup olmadığını kontrol edelim:

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

Bakalım, 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)

değiştirmeden kütüphanelerin seti değişecek mi? LD_PRELOAD Görünüşe göre, yüklenmiş olan değişken kütüphanemiz olan ld_rand.so'yu yüklemeye zorlar. Programın kendisi onu talep etmediği halde. Ve, bizim fonksiyonumuz "rand" ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış bir aylıq Bu liste OS sürümüne bağlı olarak farklılık gösterebilir, ancak kesinlikle bir dosya olmalıdıryüklenmeden önce, o da kontrolü elinde tutar.

Tamam, yerel fonksiyonu değiştirmeyi başardık, ancak fonksiyonelliğini koruyup belirli eylemleri eklemek için ne yapabiliriz? Rastgele seçimi modifiye edelim:

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

Burada "ekibimiz" olarak sadece bir metin satırı yazıyoruz, ardından orijinal fonksiyon için bir işaretçi oluşturuyoruz. ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış birBu fonksiyonun adresini almak için ihtiyacımız olacak dlsym bu, kütüphane içinden gelen bir fonksiyondur libdl, dinamik kütüphanelerdeki ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış bir bulacak. Daha sonra bu fonksiyonu çağırarak geri dönüp değerini döndürürüz. Dolayısıyla, derleme esnasında eklememiz gerekecek "-ldl" :

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

$ LD_PRELOAD=$PWD/ld_rand_evil.so ./ld_rand
Kötü niyetli kod enjekte edildi
66
Kötü niyetli kod enjekte edildi
28
Kötü niyetli kod enjekte edildi
93
Kötü niyetli kod enjekte edildi
93
Kötü niyetli kod enjekte edildi
95

Ve programımız "yerli" ve ardından programı normal bir şekilde başlatırız. Programın kendisinde tek bir satır değiştirmedik, ancak şimdi davranışı kütüphanemizdeki minik bir fonksiyona bağlıdır. Dahası, yazım esnasında yanlış bir", önceden bazı gereksiz işlemlerin gerçekleştirilmesiyle kullanıyor.

2. Axtarışın əzabları

Potensial təhdiddən xəbərdar olaraq, biz onu aşkar etmək istəyirik ki, preload yerinə yetirilmişdi. Aydındır ki, aşkar etmənin ən yaxşı yolu onu kernelə daxil etməkdir, amma məni istifadəçi səviyyəsindəki aşkar etmə variantları maraqlandırır.

Sonra bir neçə aşkar etmə həlləri və onların təkzibi ilə tanış olacağıq.

2.1. Sadədən başlayaq

Daha əvvəl qeyd edildiyi kimi, yüklənəcək kitabxananı göstərə bilmək üçün dəyişən istifadə edilə bilər LD_PRELOAD ya da onu fayla yazaraq /etc/ld.so.preload. İki sadə aşkar edici yaradacağıq.

Birincisi — quraşdırılmış mühit dəyişənini yoxlamaq üçün:

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

İkincisi — faylın açılmasını yoxlamaq üçün:

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

Kitabxanaları yükləyirik:

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

Burada və daha sonra [+] uğurlu aşkar etməyə işarə edir.
Müvafiq olaraq, [-] aşkar etmənin üstündən keçməyi bildirir.

Belə bir aşkar edicinin effektivliyi nə qədərdir? Əvvəlcə mühit dəyişənin üstündə dayanırıq:

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

Eyni ilə yoxlama prosesindən də imtina edirik Bu kütüphane sistem çağrılarını ve temel işlevleri, gibi işlemleri sağlar.:

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

Bəli, burada fayla giriş üçün başqa yollar da istifadə oluna bilər, məsələn, open64, stat və s., amma əslində onların aldatması üçün eyni 5-10 sətri kod tələb olunur.

2.2. Davam edək

Yuxarıda biz getenv() dəyəri əldə etmək üçün istifadə etdik LD_PRELOAD, lakin daha aşağı səviyyəli bir yolla daxil olmaq mümkündür ENV-deyişənlərinə. Müvəqqəti funksiyalardan istifadə etməyək, array-ə müraciət edəcəyik **environ, burada mühitin bir nüsxəsi saxlanılır:

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

Deyəsən, burada biz məlumatları birbaşa yaddaşdan oxuduğumuz üçün, belə bir çağırışı tutmaq mümkün deyil, və bizim undetect_getenv artıq müdaxiləni müəyyən etməyə mane olmayıb.

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

Göründüyü kimi bu problem həll edilmişdir? Hələlik, hər şey yeni başlayır.

Proqram başladıqdan sonra, mühit dəyişəninin dəyəri artıq hakerlər üçün lazım deyil, yəni bunu hesablamaq və icra edilmədən mütləq silmək olar. Təbii ki, yaddaşdakı array-i dəyişmək, ən azından, pis proqramlaşdırma üslubudur, amma bunu eləmək istəyən biri bizi qorumaq üçün həqiqətən dayandıracaqmı? LD_PRELOAD Bunun üçün öz saxta funksiyamızı yaratmalıyıq

init() , burada quraşdırılmış dəyişəni tutubvə onu bizim bağlayıcıya göndərərək: LD_PRELOAD İcra edirik, yoxlayırıq:

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

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

Amma, yaddaş — aşkar olması üçün sonuncu yer deyil

, hələ də var LD_PRELOAD. Aydından başlayaq /proc/Əslində, universial bir həll yolu var /proc/{PID}/environ.

undetect üçün. Problem "yanlış" davranışdadır **environ/proc/self/environunsetenv(env) düzgün variantdır.

правильный вариант

void evil_init()
{
  \/\/ İlk önce mevcut LD_PRELOAD değerini kaydedelim
  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) {
        \/\/ Mevcut LD_PRELOAD'dan kurtul
        \/\/unsetenv(env);
        \/\/ unset yerine basitçe değişkenimizi sıfırlayalım
        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
$

Ancak varsayalım ki onu bulamadık ve /proc/self/environ problemli" veriler içeriyor.

İlk önce önceki "maskemizle" deneyelim:

$ (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 dosya açma işlemi için hâlâ aynı open(), bu nedenle çözüm, zaten 2.1'de yaptığımızla benzer, ancak şimdi geçici bir dosya oluşturuyoruz, burada gerçek bellek değerlerini kopyalıyoruz ve içermeyen satırlar 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);
}

Bu aşama tamamlandı:

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

Sonraki açık yer - /proc/self/maps. Orada durmanın bir anlamı yok. Çözüm tamamen önceki ile aynı: dosyadan daha az olan verileri kopyalayarak Bu liste OS sürümüne bağlı olarak farklılık gösterebilir, ancak kesinlikle bir dosya olmalıdırld.so.

2.4. Chokepoint versiyonu

Bu çözüm, sadeliğiyle özellikle hoşuma gitti. Fonksiyonların adreslerini doğrudan karşılaştırıyoruz , render sistemi, ve "NEXT" adresleri.

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

Aşağıdaki işlemi yakalama kütüphanesini yüklüyoruz "open()" ve kontrol ediyoruz:

$ export LD_PRELOAD=$PWD\/ld_undetect_open.so
$ .\/detect_chokepoint
LD_PRELOAD (syscall - open) [+]
Libc adresi: 0x7fa86893b160
Sonraki adres: 0x7fa868a26135

Reddi daha da basit oldu:

#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. Sistem çağrıları

Görünüşe göre bu kadar ama biraz daha deneyeceğiz. Eğer sistem çağrısını doğrudan çekirdeğe yönlendirirsek, bu tüm yakalama sürecini atlamamıza olanak tanır. Aşağıdaki çözüm elbette mimariye bağlıdır (x86_64). Açma algılaması için uygulayalım: 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) [+]

Ve bu sorunun bir çözümü var. Kısa bir not man'dan:

ptrace, bir ebeveyn sürecinin başka bir süreci gözlemlemesine ve kontrol etmesine, verilerini ve kayıtlarını incelemesine ve değiştirmesine olanak tanıyan bir araçtır. Bu işlev genellikle bir hata ayıklama programında kesme noktaları oluşturmak ve sistem çağrılarını izlemek için kullanılır.

Ebeveyn süreç, ilk olarak fork(2) işlevini çağırarak izlemeye başlayabilir ve ardından oluşan çocuk süreç PTRACE_TRACEME'yi gerçekleştirebilir, ardından (genellikle) exec(3) uygulanır. Öte yandan, ebeveyn süreç mevcut bir süreci PTRACE_ATTACH ile hata ayıklamaya başlayabilir.

Bir izlenim sırasında, alt proses her seferinde bir sinyal alındığında durur, bu sinyal göz ardı edilse bile. (Tek istisna SIGKILL'dir, bu normal şekilde çalışır.) Ana proses, wait(2) çağrısı yapıldığında bunun hakkında bilgilendirilir; ardından, ana proses alt sürecin içeriğini inceleyip değiştirebilir. Daha sonra ana proses, alt prosesin çalışmasına devam etmesine izin verir; bazı durumlarda, kendisine gönderilen sinyali göz ardı edebilir ya da alternatif olarak başka bir sinyal gönderebilir.

Buna göre, çözüm, her sistem çağrısından önce süreci izlemek ve gerektiğinde akışı tuzak işlevine yönlendirmektir.

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

Yoxlayırıq:

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

+0-0=5

Çok teşekkür ederim

Charles Hubain
Chokepoint
ValdikSS
Philippe Teuwen
derhass

, makaleleri, kaynak kodları ve yorumları bu notun burada yer alması için benden çok daha fazlasını yaptı.

Mənbə: habr.com

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster