LD_PRELOAD کی تلاش ہے۔

یہ نوٹ 2014 میں لکھا گیا تھا، لیکن میں صرف Habré پر جبر کی زد میں آیا اور اس نے دن کی روشنی نہیں دیکھی۔ پابندی کے دوران میں اس کے بارے میں بھول گیا تھا، لیکن اب میں نے اسے مسودوں میں پایا۔ میں نے اسے حذف کرنے کے بارے میں سوچا، لیکن شاید یہ کسی کے کام آئے۔

LD_PRELOAD کی تلاش ہے۔

عام طور پر، "شامل" کی تلاش کے موضوع پر جمعہ کا تھوڑا سا ایڈمن پڑھ رہا ہے LD_PRELOAD.

1. ان لوگوں کے لیے جو فنکشن کے متبادل سے واقف نہیں ہیں ایک مختصر اختلاف

باقی سیدھے جا سکتے ہیں۔ п.2۔.

آئیے ایک کلاسک مثال کے ساتھ شروع کریں:

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

ہم بغیر کسی جھنڈے کے مرتب کرتے ہیں:

$ gcc ./ld_rand.c -o ld_rand

اور، جیسا کہ توقع ہے، ہمیں 5 سے کم 100 بے ترتیب نمبر ملتے ہیں:

$ ./ld_rand
53
93
48
57
20

لیکن آئیے تصور کریں کہ ہمارے پاس پروگرام کا سورس کوڈ نہیں ہے، لیکن ہمیں رویے کو تبدیل کرنے کی ضرورت ہے۔

آئیے اپنے فنکشن پروٹو ٹائپ کے ساتھ اپنی لائبریری بنائیں، مثال کے طور پر:

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

یہ چال اور بھی زیادہ متاثر کن نظر آتی ہے اگر ہم پہلے اپنی لائبریری کو اس کے ذریعے برآمد کریں۔

$ export LD_PRELOAD=$PWD/ld_rand.so

یا ہم سب سے پہلے کریں گے

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

اور پھر پروگرام کو معمول کے مطابق چلائیں۔ ہم نے پروگرام میں ہی کوڈ کی ایک لائن کو تبدیل نہیں کیا ہے، لیکن اس کا رویہ اب ہماری لائبریری کے ایک چھوٹے سے فنکشن پر منحصر ہے۔ مزید یہ کہ پروگرام لکھنے کے وقت، The رینڈ موجود بھی نہیں تھا.

ہمارے پروگرام کو کس چیز نے جعلی استعمال کیا۔ رینڈ? آئیے اسے قدم بہ قدم چلائیں۔
جب کوئی ایپلیکیشن شروع ہوتی ہے، تو کچھ لائبریریاں لوڈ ہوتی ہیں جن میں پروگرام کے لیے درکار افعال ہوتے ہیں۔ ہم ان کا استعمال کرکے دیکھ سکتے ہیں۔ ایل ڈی ڈی:

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

یہ فہرست OS ورژن کے لحاظ سے مختلف ہو سکتی ہے، لیکن وہاں ایک فائل ہونی چاہیے۔ libc.so. یہ لائبریری ہے جو سسٹم کالز اور بنیادی افعال فراہم کرتی ہے جیسے کھول, malloc, پرنف وغیرہ ہمارے رینڈ بھی ان میں سے ہے. آئیے اس بات کو یقینی بنائیں:

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

آئیے دیکھتے ہیں کہ آیا استعمال ہونے پر لائبریریوں کا سیٹ تبدیل ہوتا ہے۔ 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)

پتہ چلتا ہے کہ متغیر سیٹ ہے۔ LD_PRELOAD ہمیں لوڈ کرنے پر مجبور کرتا ہے۔ ld_rand.so یہاں تک کہ اس حقیقت کے باوجود کہ پروگرام کو خود اس کی ضرورت نہیں ہے۔ اور ہمارے فنکشن کے بعد سے "رینڈ" سے پہلے لوڈ رینڈ سے libc.so، پھر وہ مرغے پر راج کرتی ہے۔

ٹھیک ہے، ہم مقامی فنکشن کو تبدیل کرنے میں کامیاب ہو گئے، لیکن ہم یہ کیسے یقینی بنا سکتے ہیں کہ اس کی فعالیت محفوظ ہے اور کچھ اعمال شامل کیے گئے ہیں۔ آئیے اپنے بے ترتیب میں ترمیم کریں:

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

یہاں، اپنے "اضافہ" کے طور پر، ہم متن کی صرف ایک لائن پرنٹ کرتے ہیں، جس کے بعد ہم اصل فنکشن کی طرف ایک پوائنٹر بناتے ہیں۔ رینڈ. اس فنکشن کا پتہ حاصل کرنے کے لیے ہمیں درکار ہے۔ dlsym لائبریری کا ایک فنکشن ہے۔ libdlجو ہماری تلاش کرے گا رینڈ متحرک لائبریریوں کے ڈھیر میں۔ جس کے بعد ہم اس فنکشن کو کال کریں گے اور اس کی ویلیو واپس کریں گے۔ اس کے مطابق، ہمیں شامل کرنے کی ضرورت ہوگی "-ldl" اسمبلی کے دوران:

$ 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

اور ہمارا پروگرام "آبائی" استعمال کرتا ہے رینڈ, اس سے پہلے کچھ فحش حرکتیں کر چکے ہیں۔

2. تلاش کا درد

ممکنہ خطرے کے بارے میں جانتے ہوئے، ہم اس کا پتہ لگانا چاہتے ہیں۔ preload اسے انجام دیا گیا۔ یہ واضح ہے کہ پتہ لگانے کا بہترین طریقہ اسے کرنل میں دھکیلنا ہے، لیکن مجھے یوزر اسپیس میں پتہ لگانے کے اختیارات میں دلچسپی تھی۔

اگلا، پتہ لگانے اور ان کی تردید کے حل جوڑے میں آئیں گے۔

2.1 آئیے سادہ شروع کرتے ہیں۔

جیسا کہ پہلے ذکر کیا گیا ہے، آپ متغیر کا استعمال کرتے ہوئے لوڈ کرنے کے لیے لائبریری کی وضاحت کر سکتے ہیں۔ LD_PRELOAD یا اسے فائل میں لکھ کر /etc/ld.so.preload. آئیے دو سادہ ڈٹیکٹر بناتے ہیں۔

سب سے پہلے سیٹ ماحول کے متغیر کو چیک کرنا ہے:

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

دوسرا یہ چیک کرنا ہے کہ آیا فائل کھلی ہے:

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

آئیے لائبریریوں کو لوڈ کرتے ہیں:

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

یہاں اور نیچے، [+] کامیاب پتہ لگانے کی نشاندہی کرتا ہے۔
اس کے مطابق، [-] کا مطلب ہے کھوج کو نظرانداز کرنا۔

اس طرح کا پتہ لگانے والا کتنا موثر ہے؟ آئیے پہلے ماحولیاتی متغیر پر ایک نظر ڈالتے ہیں:

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

اسی طرح، ہم چیک سے چھٹکارا حاصل کرتے ہیں کھول:

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

ہاں، فائل تک رسائی کے دوسرے طریقے یہاں استعمال کیے جا سکتے ہیں، جیسے، open64, stat کی وغیرہ۔

2.2 آئیے آگے بڑھیں۔

اوپر ہم نے استعمال کیا۔ getenv() قیمت حاصل کرنے کے لئے LD_PRELOAD، لیکن وہاں تک پہنچنے کا ایک اور "نچلی سطح" کا راستہ بھی ہے۔ این وی- متغیرات ہم انٹرمیڈیٹ فنکشنز استعمال نہیں کریں گے، لیکن صف کا حوالہ دیں گے۔ ** ماحول، جو ماحول کی ایک کاپی محفوظ کرتا ہے:

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

چونکہ یہاں ہم براہ راست میموری سے ڈیٹا پڑھ رہے ہیں، اس لیے ایسی کال کو روکا نہیں جا سکتا، اور ہماری undetect_getenv یہ اب مداخلت کی شناخت میں مداخلت نہیں کرتا ہے۔

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

ایسا لگتا ہے کہ یہ مسئلہ حل ہو گیا ہے؟ ابھی تو شروعات ہے۔

پروگرام شروع ہونے کے بعد، متغیر کی قدر LD_PRELOAD ہیکرز کو اب میموری میں اس کی ضرورت نہیں ہے، یعنی وہ اسے پڑھ سکتے ہیں اور کسی بھی ہدایات پر عمل کرنے سے پہلے اسے حذف کر سکتے ہیں۔ بلاشبہ، میموری میں ایک صف میں ترمیم کرنا، کم از کم، ایک برا پروگرامنگ اسٹائل ہے، لیکن کیا یہ کسی ایسے شخص کو روک سکتا ہے جو بہرحال ہماری خیر خواہی نہیں کرتا؟

ایسا کرنے کے لیے ہمیں اپنا جعلی فنکشن بنانا ہوگا۔ اس میں()، جس میں ہم انسٹال کو روکتے ہیں۔ LD_PRELOAD اور اسے ہمارے لنکر پر بھیجیں:

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

تاہم، میموری آخری جگہ نہیں ہے جہاں آپ متبادل تلاش کرسکتے ہیں۔ LD_PRELOAD، وہاں بھی ہے /proc/. آئیے واضح کے ساتھ شروع کریں۔ /proc/{PID}/environ.

اصل میں، کے لئے ایک عالمگیر حل ہے پتہ لگانا ** ماحول и /proc/self/environ. مسئلہ "غلط" سلوک کا ہے۔ unsetenv(env).

صحیح آپشن

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
$

لیکن آئیے تصور کریں کہ ہمیں یہ نہیں ملا اور /proc/self/environ "مسئلہ" ڈیٹا پر مشتمل ہے۔

پہلے اپنے پچھلے "بھیس" کے ساتھ کوشش کریں:

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

بلی فائل کو کھولنے کے لیے وہی استعمال کرتا ہے۔ کھولیں()، لہذا حل وہی ہے جو پہلے ہی سیکشن 2.1 میں کیا گیا تھا، لیکن اب ہم ایک عارضی فائل بناتے ہیں جہاں ہم حقیقی میموری کی قدروں کو بغیر لائنوں کے کاپی کرتے ہیں۔ 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);
}

اور یہ مرحلہ گزر چکا ہے:

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

اگلی واضح جگہ ہے۔ /proc/self/maps. اس پر رہنے کا کوئی فائدہ نہیں۔ حل بالکل پچھلے حل سے ملتا جلتا ہے: فائل سے ڈیٹا کو کاپی کریں مائنس لائنوں کے درمیان libc.so и ld.so.

2.4 چوک پوائنٹ سے آپشن

مجھے خاص طور پر اس کی سادگی کے لئے یہ حل پسند آیا۔ ہم براہ راست سے بھری ہوئی فنکشنز کے پتوں کا موازنہ کرتے ہیں۔ libc، اور "اگلا" پتے۔

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

لائبریری کو مداخلت کے ساتھ لوڈ کیا جا رہا ہے۔ "کھلا ()" اور چیک کریں:

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

تردید اور بھی آسان نکلی:

#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

ایسا لگتا ہے کہ یہ سب ہے، لیکن ہم پھر بھی بھٹکیں گے۔ اگر ہم سسٹم کال کو براہ راست کرنل پر بھیجتے ہیں، تو یہ پورے مداخلت کے عمل کو نظرانداز کر دے گا۔ ذیل کا حل یقیناً فن تعمیر پر منحصر ہے (x86_64۔)۔ آئیے ایک افتتاحی کا پتہ لگانے کے لئے اسے لاگو کرنے کی کوشش کریں 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) [+]

اور اس مسئلے کا حل ہے۔ سے اقتباس آدمیA:

ptrace ایک ٹول ہے جو والدین کے عمل کو دوسرے عمل کی پیشرفت کا مشاہدہ اور کنٹرول کرنے، اس کے ڈیٹا اور رجسٹر کو دیکھنے اور تبدیل کرنے کی اجازت دیتا ہے۔ عام طور پر یہ فنکشن ڈیبگنگ پروگرام میں بریک پوائنٹس بنانے اور سسٹم کالز کی نگرانی کے لیے استعمال ہوتا ہے۔

پیرنٹ عمل پہلے فورک (2) کو کال کرکے ٹریس کرنا شروع کر سکتا ہے، اور پھر نتیجے میں بچے کا عمل PTRACE_TRACEME پر عمل درآمد کر سکتا ہے، اس کے بعد (عام طور پر) exec(3) کو چلا کر۔ دوسری طرف، پیرنٹ پروسیس PTRACE_ATTACH کا استعمال کرتے ہوئے موجودہ عمل کو ڈیبگ کرنا شروع کر سکتا ہے۔

ٹریس کرتے وقت، بچے کا عمل جب بھی سگنل ملتا ہے رک جاتا ہے، چاہے سگنل کو نظر انداز کر دیا جائے۔ (استثنیٰ SIGKILL ہے، جو عام طور پر کام کرتا ہے۔) والدین کے عمل کو انتظار(2) کو کال کرکے اس کی اطلاع دی جائے گی، جس کے بعد وہ بچے کے عمل کے شروع ہونے سے پہلے اس کے مواد کو دیکھ اور اس میں ترمیم کرسکتا ہے۔ اس کے بعد والدین کا عمل بچے کو دوڑنا جاری رکھنے کی اجازت دیتا ہے، بعض صورتوں میں اس کو بھیجے گئے سگنل کو نظر انداز کرنا یا اس کے بجائے کوئی دوسرا سگنل بھیجنا)۔

اس طرح، حل یہ ہے کہ عمل کی نگرانی کی جائے، ہر سسٹم کال سے پہلے اسے روک دیا جائے اور اگر ضروری ہو تو تھریڈ کو ہک فنکشن پر ری ڈائریکٹ کریں۔

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

جانچ پڑتال:

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

+0-0=5

بہت شکریہ

چارلس ہوبین
چوک پوائنٹ
ValdikSS
فلپ ٹیوین
derhass

جس کے مضامین، ذرائع اور تبصروں نے اس نوٹ کو یہاں ظاہر کرنے کے لیے میرے مقابلے میں بہت کچھ کیا۔

ماخذ: www.habr.com

DDoS تحفظ، VPS VDS سرورز والی سائٹوں کے لیے قابل اعتماد ہوسٹنگ خریدیں۔ DDoS تحفظ، VPS VDS سرورز کے ساتھ قابل اعتماد ویب سائٹ ہوسٹنگ خریدیں۔ ProHoster