Đang tìm kiếm LD_PRELOAD

Bức thư này được viết vào năm 2014, nhưng tôi vừa bị Habré đàn áp và nó vẫn chưa được đưa ra ánh sáng. Trong thời gian bị cấm tôi đã quên nó, nhưng bây giờ tôi tìm thấy nó trong bản nháp. Tôi đã nghĩ đến việc xóa nó đi, nhưng có lẽ nó sẽ hữu ích cho ai đó.

Đang tìm kiếm LD_PRELOAD

Nói chung Thứ Sáu chút nhé admin đọc về chủ đề tìm kiếm “bao gồm” LD_PRELOAD.

1. Một đoạn lạc đề ngắn dành cho những người chưa quen với việc thay thế hàm

Phần còn lại có thể đi thẳng đến tr.2.

Hãy bắt đầu với một ví dụ cổ điển:

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

Chúng tôi biên dịch mà không có bất kỳ cờ nào:

$ gcc ./ld_rand.c -o ld_rand

Và đúng như dự đoán, chúng ta nhận được 5 số ngẫu nhiên nhỏ hơn 100:

$ ./ld_rand
53
93
48
57
20

Nhưng hãy tưởng tượng rằng chúng ta không có mã nguồn của chương trình nhưng chúng ta cần thay đổi hành vi.

Hãy tạo thư viện của riêng chúng ta với nguyên mẫu hàm của riêng chúng ta, ví dụ:

int rand(){
  return 42;
}

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

Và bây giờ sự lựa chọn ngẫu nhiên của chúng tôi khá dễ đoán:

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

Thủ thuật này thậm chí còn ấn tượng hơn nếu lần đầu tiên chúng ta xuất thư viện của mình qua

$ export LD_PRELOAD=$PWD/ld_rand.so

hoặc chúng ta sẽ làm điều đó trước

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

rồi chạy chương trình như bình thường. Chúng tôi chưa thay đổi một dòng mã nào trong chương trình, nhưng hành vi của nó hiện phụ thuộc vào một chức năng nhỏ trong thư viện của chúng tôi. Hơn nữa, tại thời điểm viết chương trình, rand thậm chí còn không tồn tại.

Điều gì đã khiến chương trình của chúng tôi sử dụng hàng giả rand? Hãy thực hiện từng bước một.
Khi một ứng dụng khởi động, một số thư viện nhất định sẽ được tải có chứa các chức năng mà chương trình cần. Chúng ta có thể xem chúng bằng cách sử dụng 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)

Danh sách này có thể thay đổi tùy theo phiên bản hệ điều hành, nhưng phải có tệp ở đó libc.so. Thư viện này cung cấp các cuộc gọi hệ thống và các chức năng cơ bản như mở, malloc, printf v.v. của chúng tôi rand cũng nằm trong số đó. Hãy chắc chắn về điều này:

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

Hãy xem bộ thư viện có thay đổi khi sử dụng không 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)

Hóa ra biến được đặt LD_PRELOAD buộc chúng tôi phải tải ld_rand.so ngay cả khi thực tế là bản thân chương trình không yêu cầu nó. Và vì chức năng của chúng tôi "rand" tải sớm hơn rand từ libc.so, sau đó cô ấy cai trị con gà trống.

Được rồi, chúng tôi đã cố gắng thay thế hàm gốc nhưng làm cách nào để đảm bảo rằng chức năng của nó được giữ nguyên và một số hành động được thêm vào. Hãy sửa đổi ngẫu nhiên của chúng tôi:

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

Ở đây, với tư cách là phần bổ sung của chúng tôi, chúng tôi chỉ in một dòng văn bản, sau đó chúng tôi tạo một con trỏ tới hàm ban đầu rand. Để có được địa chỉ của hàm này chúng ta cần dlsym là một chức năng từ thư viện libdlcái nào sẽ tìm thấy của chúng tôi rand trong một chồng thư viện động. Sau đó chúng ta sẽ gọi hàm này và trả về giá trị của nó. Theo đó, chúng ta sẽ cần thêm "-ldl" trong quá trình lắp ráp:

$ 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

Và chương trình của chúng tôi sử dụng "bản địa" rand, trước đó đã thực hiện một số hành động tục tĩu.

2. Nỗi đau tìm kiếm

Biết về một mối đe dọa tiềm ẩn, chúng tôi muốn phát hiện ra rằng preload Nó đã được thực hiện. Rõ ràng là cách tốt nhất để phát hiện là đẩy nó vào kernel, nhưng tôi quan tâm đến các tùy chọn phát hiện trong không gian người dùng.

Tiếp theo, các giải pháp phát hiện và bác bỏ chúng sẽ đi theo từng cặp.

2.1. Hãy bắt đầu đơn giản

Như đã đề cập trước đó, bạn có thể chỉ định thư viện để tải bằng biến LD_PRELOAD hoặc bằng cách viết nó vào một tập tin /etc/ld.so.preload. Hãy tạo hai máy dò đơn giản.

Việc đầu tiên là kiểm tra biến môi trường đã đặt:

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

Cách thứ hai là kiểm tra xem file đã được mở chưa:

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

Hãy tải các thư viện:

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

Ở đây và bên dưới, [+] biểu thị việc phát hiện thành công.
Theo đó, [-] có nghĩa là bỏ qua việc phát hiện.

Máy dò như vậy có hiệu quả như thế nào? Trước tiên chúng ta hãy xem biến môi trường:

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

Tương tự, chúng ta loại bỏ việc kiểm tra mở:

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

Có, bạn có thể sử dụng các cách khác để truy cập tệp ở đây, chẳng hạn như, open64, stat v.v., nhưng trên thực tế, cần 5-10 dòng mã giống nhau để đánh lừa họ.

2.2. Tiếp tục nào

Ở trên chúng tôi đã sử dụng getenv () để có được giá trị LD_PRELOAD, nhưng cũng có một cách “cấp thấp” hơn để tiếp cận ENV-biến. Chúng ta sẽ không sử dụng các hàm trung gian mà sẽ đề cập đến mảng **môi trường, nơi lưu trữ một bản sao của môi trường:

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

Vì ở đây chúng ta đang đọc dữ liệu trực tiếp từ bộ nhớ nên cuộc gọi như vậy không thể bị chặn và không bị phát hiện_getenv nó không còn can thiệp vào việc xác định sự xâm nhập.

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

Có vẻ như vấn đề này đã được giải quyết? Nó vẫn chỉ mới bắt đầu.

Sau khi chương trình được bắt đầu, giá trị của biến LD_PRELOAD Tin tặc không còn cần nó trong bộ nhớ nữa, nghĩa là chúng có thể đọc và xóa nó trước khi thực hiện bất kỳ lệnh nào. Tất nhiên, việc chỉnh sửa một mảng trong bộ nhớ ít nhất là một phong cách lập trình tồi, nhưng làm sao điều này có thể ngăn cản một người không thực sự chúc chúng ta những điều tốt lành?

Để làm điều này, chúng ta cần tạo hàm giả của riêng mình trong đó(), trong đó chúng tôi chặn cài đặt LD_PRELOAD và chuyển nó đến trình liên kết của chúng tôi:

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

Chúng tôi thực hiện và kiểm tra:

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

Tuy nhiên, ký ức không phải là nơi cuối cùng bạn có thể tìm thấy sự thay thế LD_PRELOAD, cũng có / proc /. Hãy bắt đầu với điều hiển nhiên /proc/{PID}/môi trường.

Trên thực tế, có một giải pháp phổ quát cho không bị phát hiện **môi trường и /proc/tự/môi trường. Vấn đề là hành vi “sai” unsetenv(env).

lựa chọn chính xác

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
$

Nhưng hãy tưởng tượng rằng chúng ta không tìm thấy nó và /proc/tự/môi trường chứa dữ liệu "có vấn đề".

Đầu tiên hãy thử cách "ngụy trang" trước đây của chúng ta:

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

làm sao sử dụng tương tự để mở tập tin mở(), do đó, giải pháp tương tự như những gì đã được thực hiện trong phần 2.1, nhưng bây giờ chúng tôi tạo một tệp tạm thời nơi chúng tôi sao chép các giá trị của bộ nhớ thực mà không có dòng chứa 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);
}

Và giai đoạn này đã được thông qua:

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

Nơi rõ ràng tiếp theo là / proc / self / maps. Không có ích gì khi cứ mãi nghĩ về nó. Giải pháp hoàn toàn giống với giải pháp trước: sao chép dữ liệu từ tệp trừ đi các dòng giữa libc.so и ld.so.

2.4. Tùy chọn từ Chokepoint

Tôi đặc biệt thích giải pháp này vì tính đơn giản của nó. Chúng tôi so sánh địa chỉ của các hàm được tải trực tiếp từ libcvà địa chỉ “TIẾP THEO”.

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

Đang tải thư viện có tính năng chặn "mở()" và kiểm tra:

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

Việc bác bỏ hoá ra còn đơn giản hơn:

#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. tòa nhà chọc trời

Có vẻ như chỉ vậy thôi, nhưng chúng ta vẫn sẽ bối rối. Nếu chúng ta hướng lệnh gọi hệ thống trực tiếp tới kernel, điều này sẽ bỏ qua toàn bộ quá trình chặn. Tất nhiên, giải pháp dưới đây phụ thuộc vào kiến ​​trúc (x86_64). Hãy thử triển khai nó để phát hiện sơ hở 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) [+]

Và vấn đề này có một giải pháp. Đoạn trích từ người đàn ông'MỘT:

ptrace là một công cụ cho phép một tiến trình cha quan sát và kiểm soát tiến trình của một tiến trình khác, xem và thay đổi dữ liệu cũng như các thanh ghi của nó. Thông thường, chức năng này được sử dụng để tạo điểm dừng trong chương trình gỡ lỗi và giám sát các cuộc gọi hệ thống.

Tiến trình cha có thể bắt đầu truy tìm bằng cách gọi fork(2) đầu tiên, sau đó tiến trình con kết quả có thể thực thi PTRACE_TRACEME, theo sau (thường) là thực thi exec(3). Mặt khác, quy trình gốc có thể bắt đầu gỡ lỗi quy trình hiện có bằng PTRACE_ATTACH.

Khi theo dõi, tiến trình con sẽ dừng mỗi khi nhận được tín hiệu, ngay cả khi tín hiệu đó bị bỏ qua. (Ngoại lệ là SIGKILL, hoạt động bình thường.) Tiến trình cha sẽ được thông báo về điều này bằng cách gọi wait(2), sau đó nó có thể xem và sửa đổi nội dung của tiến trình con trước khi nó bắt đầu. Sau đó, tiến trình gốc cho phép tiến trình con tiếp tục chạy, trong một số trường hợp bỏ qua tín hiệu được gửi đến nó hoặc gửi tín hiệu khác thay thế).

Vì vậy, giải pháp là giám sát quá trình, dừng nó trước mỗi lệnh gọi hệ thống và, nếu cần, chuyển hướng luồng đến chức năng hook.

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

Chúng tôi kiểm tra:

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

+0-0=5

Cảm ơn bạn rất nhiều

Charles Hubin
điểm nghẹt thở
ValdikSS
Philippe Teuwen
đồ ăn vặt

, những bài viết, nguồn và bình luận của họ đã làm được nhiều việc hơn tôi để khiến ghi chú này xuất hiện ở đây.

Nguồn: www.habr.com

Thêm một lời nhận xét