LINUX.ORG.RU

Проблемы с перехватом sys_execve()

 , ,


0

3

В общем, пытаюсь перехватить sys_execve(), ядро ломается и все такое. Когда я проверил адреса, то меня кое что слегка удивило. А именно:

Константа __NR_execve в /usr/include/asm-generic/unistd.h равна 221, однако когда я вывожу ее в консоль во время работы модуля - мне указывается значение 59.

Ну и собственно, адрес, на который указывает sys_call_table[__NR_execve], в файле System.map, указывает на stub_execve. Однако судя по тому что написано здесь, это обычный дефайн, и, если я правильно понимаю, что такое дефайн, то у меня возникает закономерный вопрос. Почему адреса разные, если это одна и та же сущность?

На лоре только кукарекать могут. Иди лучше на специализированные форумы.

FIL ★★★★
()

однако когда я вывожу ее в консоль во время работы модуля - мне указывается значение 59

SPARC что ли? Ты не в generic смотри, а в архитектурных.

В общем, пытаюсь перехватить sys_execve(), ядро ломается и все такое.

Атрибут на запись странице с sys_call_table ставишь? Она read-only.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

Насчет SPARC не знаю (вот только что загуглил что оно такое, так что не могу ответить). А где именно смотреть? А то я пока что слишком уж новичок в ядре линукса :(

Ну само собой, я делаю таблицу RW, а после замены ставлю опять RO. В замененной функции пытаюсь сначала сделать вывод в консоль (чтобы увидеть что оно таки работает), потом опять ставлю системный вызов на место (в таблице) и выполняю вот такое:

long __res;
__asm__ volatile ("int $0x80" : "=a"(__res) : "0"(__NR_execve), "b"((long)(filename)), "c"((long)(argv)), "d"((long)(envp)));
return __res

После этого, если пытаюсь запустить какой-то процесс - комп виснет.

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Насчет SPARC не знаю

Ядро на чём пускаешь? Вроде, только у спарков __NR_execve идёт под 59 номером. Но это мелочи, ладно.

потом опять ставлю системный вызов на место (в таблице) и выполняю вот такое

Зачем, если его можно сохранить, что ты и так уже делаешь, и просто дёргать вслед за отладочной печатью?

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

На своем домашнем ПК. Или надо модель процессора? О_о А то я не совсем понял)

Когда я так делал - то комп сразу ребутнулся, если я не ошибаюсь. А когда убрал вывод в терминал, то просто перестали запускаться процессы. В общем, так тоже не канает :(.

Еще меня смущает, что в некоторых местах видел в качестве аргумента структуру с регистрами, а в доккументации (на которую кидал линк в посте), там 3 аргумента (имя файла, аргументы командной строки и переменные окружения).

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL
asmlinkage int (*old_execve)(const char *, const char **, const char **);

asmlinkage int new_execve(const char *path, const char **argv, const char **envp)
{
   printk("Yay!\n");
   return old_execve(path, argv, envp);
}

Вот что-то типа такого надо. Только перед этим old_execve = sys_call_table[__NR_execve]; сделать, разумеется.

mix_mix ★★★★★
()
Последнее исправление: mix_mix (всего исправлений: 1)
Ответ на: комментарий от mix_mix

У меня было примерно так же. Только я еще добавлял static, возвращаемое значение у меня long и аргументы объявлял вот так:

(const char __user *filename,
 const char __user *const __user *argv[],
 const char __user *const __user *envp[])
Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Нет, там без скобочек в конце.

asmlinkage long sys_execve(const char __user *filename,
                const char __user *const __user *argv,
                const char __user *const __user *envp);

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

Та вроде же как разница не принципиальная. Значения-то все равно будут одинаковые, а т.к. я не использую 2 и 3 аргументы, то разницы вроде нет. Но я на всякий случай попробовал. Пришлось опять перезагружаться)

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Да, всё верно, это я на будущее. Я, кстати, понял почему у тебя падает модуль: stub_execve в действительности не просто дефайн. Придётся либо такую же заглушечку делать, либо патчить наживую do_execve.

mix_mix ★★★★★
()
Ответ на: комментарий от Mizantrop_LoL

int $0x80

У тебя вообще какой процессор? В каком режиме работает? Для 32-битных и 64-битных вызовов могут быть разные номера

SZT ★★★★★
()
Ответ на: комментарий от mix_mix

У меня ядро 3.11. В той доккументации для моего ядра такого файла нету. И stub_execve находит дважды в одном и том же месте в виде дефайна.

А можно поподробнее об этих методах? Линки какие-то или, что было бы гораздо предпочтительнее, своими словами, чтобы я смог сразу же и переспросить, если что не пойму.

Mizantrop_LoL
() автор топика
Ответ на: комментарий от SZT

Да, я знаю, что вроде как 3 разных способа есть. Просто, так сказать, хватаюсь за любую соломинку. Да и, если я не ошибаюсь, то для совместимости, старые методы поддерживаются, так что можно вызывать таким образом. Я ведь не ошибаюсь?

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Это смотря как ядро собрать. Его можно так собрать, что 32-битные вызовы работать не будут, а 64-битные - будут

SZT ★★★★★
()
Ответ на: комментарий от SZT

Ну, я полагаю, что мой модуль должен работать на не особо экзотических ядрах-самопилках.

В любом случае - буду очень рад любым подсказкам и советам, которые помогут мне в создании этого мутанта.

Mizantrop_LoL
() автор топика
Ответ на: комментарий от mix_mix

А я-то надеялся, что поиск там нормально работает :( Большое спасибо за наводку. Буду копаться)

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Ничего там экзотического нет, просто собрать ядро без поддержки 32-бит

SZT ★★★★★
()
Ответ на: комментарий от mix_mix

Надеюсь, Вам не будет трудно помочь мне еще немного?

Собственно, я посмотрел и понял, что если подставить в обработчик адрес своей функции, то поменяются регистры/стек и данные, которые использует вызов будут испорчены? Если да, то можно ли определить функцию полностью на асме, которая будет просто переписаной stub_execve, который вместо sys_execve будет вызывать мою функцию, которая уже потом будет вызывать sys_execve и как, собственно, в С сделать такую функцию?

Ну или еще пришел в голову вариант просто подставить в call, который в stub_execve адрес своей функции, которая потом вызовет sys_execve?

Собственно, что лучше и что будет работать?

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Я бы на асме написал и слинковался. Но наилучшим здесь, мне кажется, затереть первые 10 байт sys_execve, прописать туда call/ret, push/ret, mov/jmp и т.п. с адресом хука, а в нём уже реализовать оригинал, благо адрес do_execve есть в таблице.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

Я просто боюсь трогать код функции) Это же надо еще где-то прописать и выполнить те строчки, которые затираются. А потом восстановить это дело) И в какой таблице есть адрес do_execve?

Почему этот способ предпочтительнее замены адреса sys_execve в stub_execve?

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Да ладно тебе, те же байты, вначале сохраняешь у себя, перезаписываешь чем-нибудь вроде 48 BF XX XX XX XX XX XX XX XX FF E7, потом восстановишь обратно. Не забудь asmlinkage у обработчика. Первый способ ничем не хуже, хитрее линковка только и не чувствуешь себя кулхацкером.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

А записывать, как я понял, можно только в виде цифр? Или можно как-нибудь с помощью ассемблерных команд?

И все-таки, мне любопытен вопрос насчет подмены внутри stub_execve()

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Просто структурка или uint8_t data[] и memcpy в нужное место, никакого асма. А по первому способу делаешь идентичный stub, только дёргаешь extern обработчик.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

А как быть с различием 32 и 64 разрядных команд? У одних регистры e**, а у других r**. Это ж вроде разные коды?

Я имел ввиду не функцию stub_execve переписывать, а по указателю на эту функцию, с помощью udis86 дойти до команды call и в ней подменить аргумент на указатель на мою функцию. Не проще ли будет так сделать?

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Можно использовать call, она одинаковая на обеих архитектурах, нужно только помнить, что относительная и шагает в пределах 2 GiB. Что касается метода, можно и как ты предлагаешь, и это, наверное, даже поизящней будет. Дизассемблер, в принципе, не нужен, просто ищешь 0xE8, следующие 4 байта будут знаковым смещением адреса sys_execve относительно следующей инструкции.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

А разве в 64-битной системе, адрес не будет 8-разрядным?

И как мне подставить относительный адрес функции? Можно же просто как узказатель на функцию? Типа: call myFunc;

Mizantrop_LoL
() автор топика
Ответ на: комментарий от mix_mix

И если искать 0xE8, то теоретически можно наткнуться, например, на какой-то адрес (в котором есть такой байт) или просто значение. И тогда будет беда)

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Фишка в том, что jmp/call не умеют напрямую передавать управление по 8-байтному адресу, вместо этого они используют 4-байтное смещение, то есть прыжок осуществляется до 2 GiB вперёд или назад относительно текущего адреса, чаще всего этого более, чем достаточно. Если addr — адрес инструкции, смещение будет равно myFunc-addr-5. Когда ты пишешь call myFunc, эту работу делает ассемблер.

mix_mix ★★★★★
()
Ответ на: комментарий от Mizantrop_LoL

В общем случае да, но ты же знаешь код stub_execve, и он практически не меняется. Для универсального решения, конечно, используют дизассемблер, но не полноценный, а длин, он реализуется табличкой и конечным автоматом на пару десятков строк.

mix_mix ★★★★★
()
Последнее исправление: mix_mix (всего исправлений: 1)
Ответ на: комментарий от mix_mix

Попробовал сделать - перестали запускаться процессы) Сделал просто вывод в консоль адресов. Что-то типа такого:

  uint8_t *ptr = (uint8_t *)sys_call_table[__NR_execve];
  while (*ptr++ != 0xE8)
    printk("%X\n", *ptr);
  printk("Call found!\n");
  addr_call_arg = (unsigned long)ptr;

  printk("address of sys_execve is: %p\n", *(void **)addr_call_arg);

Адрес, который оно выводит, ну совсем не тот, который в System.map. Это нормально? :(

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Потому, что мы находимся в середине инструкции вида E8 XX XX XX XX, где XX — смещение адреса перехода относительно адреса следующей инструкции. Вот, кстати, я набросал минимальный рабочий пример хука.

#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/string.h>

void **sys_call_table = (void*) 0xffffffff815241a0;

asmlinkage long (*old_execve)(char *, char **, char **);

asmlinkage long new_execve(char *path, char **argv, char **envp)
{
    printk("execve: %s\n", path);
    return old_execve(path, argv, envp);
}

int init_module()
{
    char *ptr = memchr(sys_call_table[__NR_execve], 0xE8, 200);
    if (!ptr++) return printk("Bad stub_execve\n");
    old_execve = (void*) ptr + *(int32_t*) ptr + 4;
    *(int32_t*) ptr = (char*) new_execve - ptr - 4;
    return 0;
}

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

После которого надо делать ребут)

Т.е., если я правильно понял, семантически правильнее, адрес будет выглядеть так: ptr + 4 + *(int32_t *)ptr ? Т.е. адрес след. команды (ptr + 4) плюс смещение (*(int32_t *)ptr)?

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

У меня отлично работает. Ты же поменял адрес sys_call_table, надеюсь? Наверное, правильнее, но сложение ассоциативно вообще.

mix_mix ★★★★★
()
Последнее исправление: mix_mix (всего исправлений: 1)
Ответ на: комментарий от mix_mix

Я имел ввиду, что этот модуль не возвращает все на место)

Ну адрес sys_call_table у меня во время выполнения ищется. А функции для замены и возврата вот такие:

int patchStubExecve()
{
  uint8_t *ptr = memchr(sys_call_table[__NR_execve], 0xE8, 200);
  ptr++;
  
  addr_call_arg = (unsigned long)ptr;
    
  sysExecve = (void *)(addr_call_arg + 4 + *(unsigned long *)addr_call_arg);
  *((unsigned long *)addr_call_arg) = (unsigned long)((uint8_t *)fakeExecve - addr_call_arg - 4);
  
  return 0;
}

int unpatchStubExecve()
{
  *((unsigned long *)addr_call_arg) = (unsigned long)((uint8_t *)sysExecve - addr_call_arg - 4);
  
  return 0;
}

И да, я таки опять сломал ядро. Таки что-то в этом коде я напартачил :(

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

А, ну да, за собой не прибирает. Напортачил в том, что во-первых, смещение не long, а во-вторых не unsigned.

mix_mix ★★★★★
()
Ответ на: комментарий от Mizantrop_LoL

Нет, этот код будет работать как под 32 битами, так и под 64. Обрати внимание на касты в примере выше.

old_execve = (void*) ptr + *(int32_t*) ptr + 4;
*(int32_t*) ptr = (char*) new_execve - ptr - 4;

mix_mix ★★★★★
()
Последнее исправление: mix_mix (всего исправлений: 1)
Ответ на: комментарий от mix_mix

Я сделал вот так, чтобы не ругалось на несоответствие типов, однако на вторую строчку все равно ругается :(

 sysExecve = (void *)((void *)addr_call_arg + 4 + *(int32_t *)addr_call_arg);
  *((int32_t *)addr_call_arg) = (int32_t)((uint8_t *)fakeExecve - addr_call_arg - 4);

Пы.Сы. Но зато теперь все работает! Большое спасибо за помощь)

Mizantrop_LoL
() автор топика
Ответ на: комментарий от Mizantrop_LoL

Потому что ты из адреса вычитаешь число, получая адрес и кастуешь к числу. Ты можешь обойтись вообще без addr_call_arg, довольствуясь одним ptr. Возможно когда-нибудь потом имеет смысл вместо memchr действительно дизассемблер длин задействовать, если хочется универсальности. Покажи, кстати, как sys_call_table ищешь, мне интересно.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

Пока что вот такой костыль.

unsigned long **findSysCallTable(void)
{
    unsigned long ptr;
    unsigned long *p;
    
    for (ptr = (unsigned long)sys_close;
	 ptr < (unsigned long)&loops_per_jiffy;
	 ptr += sizeof(unsigned long))
    {
      p = (unsigned long *)ptr;
      
      if (p[__NR_close] == (unsigned long)sys_close)
      {
	return (unsigned long **)p;
      }
    }
    
    return NULL;
}

Однако находил в Инете более кошерный способ. Но решил сначала разобраться с главной проблемой, а потом уже причесывать.

Mizantrop_LoL
() автор топика
Ответ на: комментарий от mix_mix

Можно использовать call, она одинаковая на обеих архитектурах, нужно только помнить, что относительная и шагает в пределах 2 GiB.

Это не совсем так. Есть инструкция call, которая может вызывать функцию, адрес которой в 64-битном регистре, без всяких там относительных смещений

int call64()
{
  (*(void(*)())0xFFFFFFFFFFFFFFFFull)();
  return 0;
}

call64():
	subq	$8, %rsp
	movq	$-1, %rax
	call	*%rax
	xorl	%eax, %eax
	addq	$8, %rsp
	ret

SZT ★★★★★
()
Последнее исправление: SZT (всего исправлений: 1)
Ответ на: комментарий от SZT

Речь была исключительно про прямую адресацию, но ты полностью прав, я чуть выше как раз использовал схему c jmp *%rdi. Из недостатков, очевидно, необходим регистр. Кстати, вариант с заталкиванием адреса в стек и уходом на него по ret мне больше нравится.

mix_mix ★★★★★
()
Ответ на: комментарий от mix_mix

Кстати, вариант с заталкиванием адреса в стек и уходом на него по ret мне больше нравится.

запихнуть 64-бит в стек через инструкцию push не выйдет, надо записывать через mov, а еще рассогласование call/ret будет, это не очень хорошо для производительности

SZT ★★★★★
()
Ответ на: комментарий от mix_mix

http://www.agner.org/optimize/microarchitecture.pdf page 34

The P1 has no return stack buffer, but uses the same method for returns as for indirect jumps. Later processors have a return stack buffer. The size of this buffer is 4 in the PMMX, 8 in Atom, 12 in AMD k8, 16 in PPro, P2, P3, P4, P4E, PM, Core2 and Nehalem, and 24 in AMD k10. This size may seem rather small, but it is sufficient in most cases because only the innermost subroutines matter in terms of execution time. The return stack buffer may be insufficient, though, in the case of a deeply nesting recursive function. In order to make this mechanism work, you must make sure that all calls are matched with returns. Never jump out of a subroutine without a return and never use a return as an indirect jump.

SZT ★★★★★
()
Ответ на: комментарий от SZT

По частям же, в два push. Ну либо через mov, вручную опустив rsp. А про рассогласование я, увы, ничего не знаю. Есть что почитать?

mix_mix ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.