LD_PRELOADን በመፈለግ ላይ

ይህ ማስታወሻ የተፃፈው እ.ኤ.አ. በ 2014 ነው ፣ ግን በሀበሬ ላይ ጭቆና ውስጥ ገባሁ እና የቀን ብርሃን አላየም። በእገዳው ጊዜ ረስቼው ነበር, አሁን ግን በረቂቆች ውስጥ አገኘሁት. እሱን ለመሰረዝ አሰብኩ ፣ ግን ምናልባት ለአንድ ሰው ጠቃሚ ሊሆን ይችላል።

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

እና ከዚያ እንደተለመደው ፕሮግራሙን ያሂዱ. በፕሮግራሙ ውስጥ አንድ ነጠላ የኮድ መስመር አልቀየርንም፣ ነገር ግን ባህሪው አሁን በቤተ-መጽሐፍታችን ውስጥ ባለ ትንሽ ተግባር ላይ የተመሰረተ ነው። ከዚህም በላይ ፕሮግራሙን በሚጽፉበት ጊዜ እ.ኤ.አ ራንድ እንኳን አልነበረም።

ፕሮግራማችን የውሸት እንዲጠቀም ያደረገው ራንድ? ደረጃ በደረጃ እንውሰድ።
አፕሊኬሽኑ ሲጀመር በፕሮግራሙ የሚያስፈልጉትን ተግባራት የሚያካትቱ የተወሰኑ ቤተ-መጻሕፍት ይጫናሉ። ተጠቅመን ልንመለከታቸው እንችላለን :

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

ይህ ዝርዝር እንደ የስርዓተ ክወናው ስሪት ሊለያይ ይችላል, ነገር ግን እዚያ ፋይል መኖር አለበት libc.so. የስርዓት ጥሪዎችን እና እንደ መሰረታዊ ተግባራትን የሚያቀርበው ይህ ቤተ-መጽሐፍት ነው። ክፍት, ማልቦል, printf ወዘተ የኛ ራንድ ከነሱ መካከልም አለ። ይህንን እናረጋግጥ፡-

# 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_ራንድ.ሶ ምንም እንኳን ፕሮግራሙ ራሱ ባይፈልግም. እና ከተግባራችን ጀምሮ "ራንድ" ቀደም ብሎ ይጫናል ራንድ от 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. የመፈለግ ህመም

ሊከሰት ስለሚችል ስጋት በማወቅ፣ ያንን ማወቅ እንፈልጋለን ጫን ተፈጽሟል። ለማወቅ ምርጡ መንገድ ወደ ከርነል መግፋት እንደሆነ ግልጽ ነው፣ ነገር ግን በተጠቃሚ ቦታ ውስጥ ያሉትን የመለየት አማራጮች ፍላጎት ነበረኝ።

በመቀጠል፣ የመለየት መፍትሄዎች እና የእነሱ ውድቀቶች ጥንድ ሆነው ይመጣሉ።

2.1. ቀላል እንጀምር

ቀደም ሲል እንደተጠቀሰው, ተለዋዋጭውን በመጠቀም ለመጫን ቤተ-መጽሐፍቱን መግለጽ ይችላሉ LD_PRELOAD ወይም በፋይል ውስጥ በመጻፍ /ወዘተ/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. እንቀጥል

ከላይ ተጠቀምን። ጌቴንቭ() ዋጋ ለማግኘት LD_PRELOAD, ነገር ግን ለመድረስ የበለጠ "ዝቅተኛ-ደረጃ" መንገድም አለ ENV-ተለዋዋጮች. መካከለኛ ተግባራትን አንጠቀምም፣ ግን ድርድርን እንጠቅሳለን። ** አካባቢየአካባቢን ቅጂ የሚያከማች፡

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

እዚህ እኛ በቀጥታ ከማህደረ ትውስታ መረጃን እያነበብን ስለሆነ, እንደዚህ አይነት ጥሪ ሊጠለፍ አይችልም, እና የእኛ ያልታወቀ_ጌቴንቭ ከአሁን በኋላ ወረራውን በመለየት ላይ ጣልቃ አይገባም.

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

ነገር ግን, ማህደረ ትውስታ ምትክ ማግኘት የሚችሉበት የመጨረሻው ቦታ አይደለም LD_PRELOAD, እንዲሁም አለ /proc/. ግልጽ በሆነው ነገር እንጀምር /proc/{PID}/አካባቢ.

እንደ እውነቱ ከሆነ, ለ ሁለንተናዊ መፍትሔ አለ የማይታወቅ ** አካባቢ и /proc/ራስ/ አካባቢ. ችግሩ "የተሳሳተ" ባህሪ ነው 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/ራስ/ አካባቢ "ችግር ያለበት" ውሂብ ይዟል.

መጀመሪያ በቀደመው “ማስመሰል” እንሞክር፡-

$ (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/ካርታዎች. በእሱ ላይ መኖር ምንም ፋይዳ የለውም. መፍትሄው ከቀዳሚው ጋር ፍጹም ተመሳሳይ ነው፡ በመካከላቸው ያሉትን መስመሮች በመቀነስ ውሂቡን ከፋይሉ ይቅዱ libc.so и ld.

2.4. ከ Chokepoint አማራጭ

በተለይ ይህን መፍትሄ ለቀላልነቱ ወድጄዋለሁ። በቀጥታ የተጫኑትን ተግባራት አድራሻዎች እናነፃፅራለን ልሳ, እና "ቀጣይ" አድራሻዎች.

#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.ቅድመ ጭነት.

#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
Philippe Teuwen
ድርሃስ

ይህ ማስታወሻ እዚህ እንዲታይ እኔ ካደረግኩት በላይ ጽሑፎቻቸው፣ ምንጮቻቸው እና አስተያየቶቻቸው ብዙ አድርገዋል።

ምንጭ: hab.com

አስተያየት ያክሉ