LD_PRELOAD ကို ရှာဖွေနေပါသည်။

ဒီမှတ်စုကို 2014 မှာရေးခဲ့ပေမဲ့ Habre အပေါ်မှာ ဖိနှိပ်ချုပ်ချယ်ခံခဲ့ရပြီး နေ့အလင်းရောင်ကို မမြင်ခဲ့ရပါဘူး။ ပိတ်ပင်ထားစဉ်အတွင်း ၎င်းကို မေ့သွားသော်လည်း ယခုမူကြမ်းများတွင် တွေ့ရှိရပါသည်။ အဲဒါကို ဖျက်ပစ်ဖို့ စိတ်ကူးမိပေမယ့် တစ်စုံတစ်ယောက်အတွက် အသုံးဝင်မယ်ထင်တယ်။

LD_PRELOAD ကို ရှာဖွေနေပါသည်။

ယေဘုယျအားဖြင့်၊ "ပါဝင်သည်" ကိုရှာဖွေခြင်း၏ခေါင်းစဉ်အပေါ်သောကြာနေ့တွင်အနည်းငယ်သောစီမံခန့်ခွဲသူဖတ်ခြင်း LD_PRELOAD.

1. function အစားထိုးခြင်းနှင့် မရင်းနှီးသူများအတွက် တိုတောင်းသော digression

ကျန်တာတွေ တည့်တည့်သွားနိုင်တယ်။ စ.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)

Этот список может быть различен в зависимости от версии OS, но там обязательно должен быть файл 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 ငါတို့ကို load လုပ်ခိုင်းတယ်။ ld_rand.so ပရိုဂရမ်ကိုယ်တိုင်က မလိုအပ်ပေမယ့်၊ နောက်ပြီးတော့ ကျွန်တော်တို့ function ကစလို့ပေါ့။ "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();
}

ဤနေရာတွင် ကျွန်ုပ်တို့၏ “ထပ်တိုးမှု” အနေဖြင့်၊ ကျွန်ုပ်တို့သည် စာသားတစ်ကြောင်းတည်းကိုသာ ပရင့်ထုတ်ပြီးနောက် မူရင်းလုပ်ဆောင်ချက်အတွက် ညွှန်ပြချက်ကို ဖန်တီးပါသည်။ ကျပန်း. ဤလုပ်ဆောင်ချက်၏လိပ်စာကိုရရှိရန်ကျွန်ုပ်တို့လိုအပ်သည်။ 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 သို့မဟုတ် ဖိုင်တစ်ခုတွင် ရေးခြင်းဖြင့် /etc/ld.so.preload. ရိုးရှင်းသော detector နှစ်ခုဖန်တီးကြပါစို့။

ပထမတစ်ခုသည် set environment variable ကိုစစ်ဆေးရန်ဖြစ်သည်

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

Здесь и далее [+] указывает на успешное обнаружение.
ထို့ကြောင့်၊

ထိုသို့သော detector သည်မည်မျှထိရောက်သနည်း။ ပထမဦးစွာ Environment variable ကို ကြည့်ရအောင်။

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

Да, здесь могут быть использованы другие способы доступа к файлу, такие как, ဖွင့်ပါ ၂, Stat စသည်ဖြင့်၊ သို့သော်၊ တကယ်တမ်းတွင်၊ ၎င်းတို့ကိုလှည့်ဖြားရန် တူညီသောကုဒ် ၅-၁၀ ကြောင်း လိုအပ်ပါသည်။

၂.၂။ ဆက်ကြရအောင်

အထက်က ကျနော်တို့ သုံးတယ်။ getenv() တန်ဖိုးကိုရယူရန် LD_PRELOAD, но есть же и более «низкоуровневый» способ добраться до ENV-ကိန်းရှင်များ။ ကျွန်ုပ်တို့သည် အလယ်အလတ်လုပ်ဆောင်ချက်များကို အသုံးမပြုသော်လည်း array ကို ရည်ညွှန်းပါမည်။ **ပတ်ဝန်းကျင်, в котором хранится копия окружения:

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

ဒီပြဿနာကို ဖြေရှင်းပြီးပြီလို့ ထင်ပါသလား။ အခုမှပဲစပါသေးတယ်။

ပရိုဂရမ်ကိုစတင်ပြီးနောက် variable ၏တန်ဖိုး LD_PRELOAD ဟက်ကာများသည် ၎င်းကို မှတ်ဉာဏ်တွင် မလိုအပ်တော့ဘဲ၊ ဆိုလိုသည်မှာ ၎င်းတို့သည် ညွှန်ကြားချက်များကို မလုပ်ဆောင်မီ ၎င်းကို ဖတ်ပြီး ဖျက်ပစ်နိုင်သည်။ သေချာပါတယ်၊ မန်မိုရီထဲက ခင်းကျင်းတစ်ခုကို တည်းဖြတ်တာဟာ အနည်းဆုံးတော့ ဆိုးရွားတဲ့ ပရိုဂရမ်ရေးဟန်တစ်ခုပါပဲ၊ ဒါပေမယ့် ငါတို့ကို ကောင်းကောင်းမလိုလားတဲ့သူကို ဒါကို ဘယ်လိုတားဆီးနိုင်မလဲ။

ဒါကိုလုပ်ဖို့ ကျွန်ုပ်တို့ရဲ့ ကိုယ်ပိုင် function အတုကို ဖန်တီးရပါမယ်။ 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) [-]

၂.၃။ /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

ကွောငျ ဖိုင်ကိုဖွင့်ရန် အလားတူအသုံးပြုသည်။ open(), поэтому решение сходно с тем, что уже делалось в п.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.

၂.၄။ Chokepoint မှရွေးချယ်မှု

ဤဖြေရှင်းချက်ကို ၎င်း၏ရိုးရှင်းမှုအတွက် အထူးနှစ်သက်ပါသည်။ ကျွန်ုပ်တို့သည် တိုက်ရိုက်တင်ထားသော လုပ်ဆောင်ချက်များ၏ လိပ်စာများကို နှိုင်းယှဉ်ပါသည်။ libcနှင့် "NEXT" လိပ်စာများ။

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

ကြားဖြတ်ဖြင့် စာကြည့်တိုက်ကို တင်နေပါသည်။ «open()» и проверяем:

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

၂.၅။ စာတိုများ

Казалось бы на этом всё, но ещё побарахтаемся. Если мы направим системный вызов напрямую к ядру, то это позволит обойти весь процесс перехвата. Решение ниже, разумеется, архитектурозависимое (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) ကိုခေါ်ဆိုခြင်းဖြင့် ၎င်းကို မိဘလုပ်ငန်းစဥ်အား အကြောင်းကြားမည်ဖြစ်ပြီး ၎င်းသည် မစတင်မီ ကလေးလုပ်ငန်းစဉ်၏ အကြောင်းအရာများကို ကြည့်ရှုပြင်ဆင်နိုင်မည်ဖြစ်သည်။ ထို့နောက် မိဘလုပ်ငန်းစဥ်သည် ကလေးအား ဆက်လက်လည်ပတ်နိုင်စေရန် ခွင့်ပြုပေးသည်၊ အချို့ကိစ္စများတွင် ၎င်းထံသို့ ပေးပို့သည့်အချက်ပြမှုကို လျစ်လျူရှုခြင်း သို့မဟုတ် အခြားအချက်ပြမှုကို ပေးပို့ခြင်းအစား)။

ထို့ကြောင့်၊ ဖြေရှင်းချက်သည် လုပ်ငန်းစဉ်ကို စောင့်ကြည့်ရန်ဖြစ်ပြီး စနစ်တစ်ခုစီမခေါ်ဆိုမီ ၎င်းကို ရပ်တန့်ရန်နှင့် လိုအပ်ပါက thread အား ချိတ်လုပ်ဆောင်ချက်သို့ ပြန်ညွှန်းရန်ဖြစ်သည်။

#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

အတော်များများကကျေးဇူးတင်စကား

Charles Hubain
choke point
ValdikSS
Philippe Teuwen
derhass

ဤမှတ်စုကို ဤနေရာတွင် ပေါ်လာစေရန် ကျွန်ုပ်လုပ်ခဲ့သည်ထက် များစွာပိုသော ဆောင်းပါးများ၊ အရင်းအမြစ်များနှင့် မှတ်ချက်များ။

source: www.habr.com

မှတ်ချက် Add