LD_PRELOAD aranıyor

Bu not 2014 yılında yazıldı ama Habré'ye yönelik baskıya yeni maruz kaldım ve gün yüzü görmedi. Yasak sırasında unutmuştum ama şimdi taslaklarda buldum. Silmeyi düşündüm ama belki birisinin işine yarar.

LD_PRELOAD aranıyor

Genel olarak, Cuma günü yöneticinin "dahil" araması konusunu okuması LD_PRELOAD.

1. İşlev ikamesine aşina olmayanlar için kısa bir açıklama

Geri kalanı doğrudan şu adrese gidebilir: p.2.

Klasik bir örnekle başlayalım:

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

Herhangi bir bayrak olmadan derliyoruz:

$ gcc ./ld_rand.c -o ld_rand

Ve beklendiği gibi 5'den küçük 100 rastgele sayı elde ediyoruz:

$ ./ld_rand
53
93
48
57
20

Ancak programın kaynak koduna sahip olmadığımızı ancak davranışı değiştirmemiz gerektiğini düşünelim.

Kendi fonksiyon prototipimizle kendi kütüphanemizi oluşturalım, örneğin:

int rand(){
  return 42;
}

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

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

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

İlk önce kütüphanemizi şu adresten dışa aktarırsak, bu numara daha da etkileyici görünür:

$ export LD_PRELOAD=$PWD/ld_rand.so

yoksa önce biz yaparız

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

ve ardından programı her zamanki gibi çalıştırın. Programın kendisinde tek bir kod satırını bile değiştirmedik ama davranışı artık kütüphanemizdeki küçük bir fonksiyona bağlı. Ayrıca programın yazıldığı sırada tüm mevcut bile değildi.

Programımızın sahte kullanmasına neden olan şey nedir? tüm? Adım adım ele alalım.
Bir uygulama başlatıldığında programın ihtiyaç duyduğu işlevleri içeren belirli kütüphaneler yüklenir. Bunları kullanarak görüntüleyebiliriz dd:

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

Bu liste işletim sistemi sürümüne göre değişebilir ancak orada bir dosya bulunmalıdır libc.so. Sistem çağrılarını ve temel işlevleri sağlayan bu kütüphanedir. açık, malloc, printf vb. Bizim tüm da bunların arasında yer alıyor. Şundan emin olalım:

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

Kullanıldığında kütüphane kümesinin değişip değişmeyeceğini görelim 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şkenin ayarlandığı ortaya çıktı LD_PRELOAD yüklememizi zorluyor ld_rand.so programın kendisinin bunu gerektirmemesine rağmen. Ve fonksiyonumuzdan beri "rand" daha erken yükler tüm itibaren libc.so, o zaman tünekleri yönetir.

Tamam, yerel işlevi değiştirmeyi başardık, ancak işlevselliğinin korunduğundan ve bazı eylemlerin eklendiğinden nasıl emin olabiliriz? Rastgelemizi değiştirelim:

#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 "eklememiz" olarak yalnızca bir satır metin yazdırıyoruz, ardından orijinal fonksiyona bir işaretçi oluşturuyoruz tüm. Bu fonksiyonun adresini elde etmek için ihtiyacımız var dlsym kütüphaneden bir fonksiyondur libdlhangisi bizi bulacak tüm bir dinamik kitaplık yığınında. Daha sonra bu fonksiyonu çağırıp değerini döndüreceğiz. Buna göre şunu eklememiz gerekecek: "-ldl" montaj sırasında:

$ 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

Ve programımız "yerel" kullanıyor tüm, daha önce bazı müstehcen eylemler gerçekleştirmiş olmak.

2. Aramanın acısı

Potansiyel bir tehdidi bildiğimiz için bunu tespit etmek istiyoruz preload Gerçekleştirildi. Tespit etmenin en iyi yolunun onu çekirdeğe itmek olduğu açık, ancak ben kullanıcı alanındaki tespit seçenekleriyle ilgileniyordum.

Daha sonra tespit ve bunların çürütülmesine yönelik çözümler çiftler halinde gelecektir.

2.1. Basit başlayalım

Daha önce de belirtildiği gibi, değişkeni kullanarak yüklenecek kitaplığı belirtebilirsiniz. LD_PRELOAD veya bir dosyaya yazarak /etc/ld.so.preload. İki basit dedektör oluşturalım.

İlki ayarlanan ortam değişkenini kontrol etmektir:

#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 ise dosyanın açılıp açılmadığını kontrol etmektir:

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

Kütüphaneleri yükleyelim:

$ 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 ve altında, [+] başarılı algılamayı gösterir.
Buna göre, [-], tespitin atlanması anlamına gelir.

Böyle bir dedektör ne kadar etkilidir? Önce ortam değişkenine bir göz atalım:

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

Benzer şekilde çekten de kurtuluyoruz açık:

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

Evet, dosyaya erişmenin başka yolları da burada kullanılabilir, örneğin: open64, stat vb. ama aslında onları kandırmak için aynı 5-10 satırlık koda ihtiyaç var.

2.2. Hadi devam edelim

Yukarıda kullandığımız getenv() değeri elde etmek için LD_PRELOADancak buna ulaşmanın daha “düşük seviyeli” bir yolu da var. ENV-değişkenler. Ara fonksiyonları kullanmayacağız ancak diziye başvuracağız **çevreortamın bir kopyasını saklayan:

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

Burada veriyi doğrudan hafızadan okuduğumuz için böyle bir çağrıya müdahale edilemez ve undetect_getenv artık izinsiz girişin tanımlanmasına müdahale etmez.

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

Bu sorun çözülmüş gibi görünüyor? Henüz yeni başlıyor.

Program başlatıldıktan sonra değişkenin değeri LD_PRELOAD Bilgisayar korsanlarının artık hafızasında buna ihtiyacı yok, yani herhangi bir talimatı uygulamadan önce onu okuyabilir ve silebilir. Elbette, bellekteki bir diziyi düzenlemek en azından kötü bir programlama stilidir, ancak bu bizim iyiliğimizi istemeyen birini nasıl durdurabilir ki?

Bunu yapmak için kendi sahte fonksiyonumuzu yaratmamız gerekiyor. içinde(), burada kurulu olanı durduruyoruz LD_PRELOAD ve bağlayıcımıza iletin:

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

Çalıştırıyoruz ve kontrol ediyoruz:

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

Ancak hafıza, bir ikameyi bulabileceğiniz son yer değildir. LD_PRELOAD, Ayrıca birde şu var /proc/. Açık olanla başlayalım /proc/{PID}/ortam.

Aslında evrensel bir çözüm var. tespit etmemek **çevre и /proc/kendi/çevre. Sorun “yanlış” davranıştır unsetenv(env).

doğru seçenek

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
$

Ama onu bulamadığımızı hayal edelim ve /proc/kendi/çevre "sorunlu" veriler içerir.

İlk önce önceki "kılık değiştirmemizi" 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

kedi dosyayı açmak için aynısını kullanır açık(), yani çözüm bölüm 2.1'de yapılana benzer, ancak şimdi gerçek belleğin değerlerini içeren satırlar olmadan kopyaladığımız geçici bir dosya oluşturuyoruz. 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);
}

Ve bu aşama geçildi:

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

Bir sonraki bariz yer /proc/self/haritalar. Bunun üzerinde durmanın bir anlamı yok. Çözüm bir öncekiyle tamamen aynı: dosyadaki verileri aradaki satırlar hariç kopyalayın libc.so и ld.so.

2.4. Chokepoint'ten Seçenek

Bu çözümü özellikle basitliği nedeniyle beğendim. Doğrudan yüklenen işlevlerin adreslerini karşılaştırırız. libcve “SONRAKİ” adresler.

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

Kitaplığı müdahaleyle yükleme "açık()" ve şunu kontrol edin:

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

Çürütmenin daha da basit olduğu ortaya çıktı:

#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 hepsi bu, ama yine de bocalayacağız. Sistem çağrısını doğrudan çekirdeğe yönlendirirsek bu, tüm müdahale sürecini atlayacaktır. Aşağıdaki çözüm elbette mimariye bağlıdır (x86_64). Bir açıklığı tespit etmek için bunu uygulamaya çalışalım ld.so.önyükleme.

#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. Alıntı adam'A:

ptrace, bir ana sürecin başka bir sürecin ilerleyişini gözlemlemesine ve kontrol etmesine, verilerini ve kayıtlarını görüntülemesine ve değiştirmesine olanak tanıyan bir araçtır. Tipik olarak bu işlev, bir hata ayıklama programında kesme noktaları oluşturmak ve sistem çağrılarını izlemek için kullanılır.

Ana süreç ilk önce fork(2)'yi çağırarak izlemeye başlayabilir ve daha sonra ortaya çıkan alt süreç PTRACE_TRACEME'yi çalıştırabilir, ardından (genellikle) exec(3)'ü çalıştırabilir. Öte yandan, bir ana süreç PTRACE_ATTACH kullanarak mevcut bir süreçte hata ayıklamaya başlayabilir.

İzleme sırasında alt süreç, sinyal göz ardı edilse bile her sinyal aldığında durur. (İstisna, normal çalışan SIGKILL'dir.) Ana süreç, wait(2) çağrılarak bu konuda bilgilendirilir, ardından alt süreç başlamadan önce içeriğini görüntüleyebilir ve değiştirebilir. Ana süreç daha sonra çocuğun koşmaya devam etmesine, bazı durumlarda kendisine gönderilen sinyali göz ardı etmesine veya bunun yerine başka bir sinyal göndermesine izin verir.

Bu nedenle çözüm, süreci izlemek, her sistem çağrısından önce durdurmak ve gerekirse iş parçacığını bir kanca 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);
    }
}

Kontrol ediyoruz:

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

+0-0=5

Çok teşekkürler

Charles Hubain
boğucu nokta
ValdikSS
Philippe Teuwen
derhas

Bu notun burada yer alması için makaleleri, kaynakları ve yorumları benden çok daha fazlasını yapan kişi.

Kaynak: habr.com

Yorum ekle