Как system () влияет на стек в x64 linux? - PullRequest
0 голосов
/ 23 апреля 2020

Я читаю превосходную книгу Джона Эриксона "Взлом: искусство эксплуатации" и пытаюсь понять его изложение переполнения буфера. Книга кажется немного устаревшей; в его примерах он работает под управлением x86 linux, и у меня возникают проблемы с репликацией результатов на x64 (я знаю, что в последние годы была добавлена ​​большая защита стека). В частности, я изо всех сил пытаюсь воспроизвести его exploit_notesearch.c программу.

В начале своей книги он продемонстрировал программу notesearch.c, которая запускает suid root и имеет следующие начальные строки после включения в библиотеку и объявления функций:

int main(int argc, char *argv[]) {
    int userid, printing=1, fd;
    char searchstring[100];

    if(argc>1)
        strcpy(searchstring, argv[1]);
    else
        searchstring[0]=0;
...

Теперь Эриксон позже демонстрирует эксплойт для этой программы под названием exploit_notesearch.c, скелет которого:

char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a"
"\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69"
"\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80";

int main(int argc, char *argv[]) {
    unsigned int i, *ptr, ret, offset=170;
    char *command, *buffer;
...
    if(argc>1)
        offset=atoi(argv[1]);
    ret=(unsigned int) &i-offset;
...
    system(command);
    free(command);
}

Области, которые я выбрал, просто копируют правильные данные в command, начиная с записи "./notesearch '", затем вставляя 60 NOP байтов, затем вставляя данные, хранящиеся в shellcode, а затем заполняя оставшуюся часть выделенной памяти адресом ret и заканчивая строка с '.

Насколько я понимаю, идея эксплойта должна заключаться в следующем. После выполнения строки system(command) система поместит в стек новый sh новый кадр стека для функции main с notesearch. Внизу этого стекового фрейма находится адрес, к которому EIP должен возвращаться после завершения основной функции, а где-то посередине - место, выделенное для буфера searchstring. ret предназначен для аппроксимации начала пространства, выделенного для searchstring, который мы перезаписываем с помощью инструкций NOP (в качестве коэффициента выдумки), шелл-кода (который при выполнении открывает оболочку root), а затем десятков копий адреса ret, чтобы мы перезаписали адрес возврата для EIP. Система выполняет main как обычно, но затем вместо возврата к адресу в коде exploit_notesearch возвращается к адресу ret и продолжает выполнение шелл-кода по желанию. Идея определения ret заключается в том, что i живет где-то в кадре стека непосредственно над кадром стека для функции main, равной notesearch.c, поэтому searchstring не должен находиться слишком далеко от i, и следовательно, экспериментируя с различными смещениями, мы сможем найти тот, который работает. (Сани NOP означают, что мы не должны быть абсолютно точными.)

Я думаю, что я в основном понял это правильно, но есть несколько проблем. Принципиальным является то, что мое понимание того, как работает system(), основано на догадках, поскольку Эриксон не подробно описывает, как это работает. Чтобы понять, что происходит, я попытался переписать эту программу для совместимости с x64 linux, внеся следующие изменения:

  • , заменив код, указанный Эриксоном, на код, написанный для x64
  • изменив i, ret и offset на длинные беззнаковые целые числа и изменив приращение i в for l oop на 8, чтобы учесть большие адреса памяти
  • компиляция notesearch.c и exploit_notesearch.c с флагом -fno-stack-protector

Однако это не сработало вообще, поэтому для отладки я добавил строку к notesearch.c, которая печатает адрес searchstring и строка exploit_notesearch.c, которая печатает адрес ret. После запуска ./exploit_notesearch несколько раз я получал странные результаты:

trial 1:
ret:          0x7ffdc21f25ce
searchstring: 0x7ffee3c209a0

trial 2:
ret:          0x7fff6115703e
searchstring: 0x7ffd1233afb0

trial 3:
ret:          0x7ffeab00781e
searchstring: 0x7fff3c8a8760

Итак, что здесь происходит? Кажется, что вызов system() изменяет стек действительно непредсказуемым образом, иногда помещая новый кадр стека ниже старого, а иногда выше. Отладка с помощью gdb не помогла, так как кажется, что весь вызов system() объединен в одну строку call 0x555555554710 <system@plt>, которая не дала никакой информации.

Итак, мои основные вопросы - это :

  • как команды оболочки, вызываемые с system(), взаимодействуют со стеком?
  • это сделано в x64 linux иначе, чем в x86, или я действительно неправильно понял код, написанный Эриксоном?
  • есть ли способ отключить эти меры безопасности на x64 linux при компиляции, чтобы я мог следить за кодом Эриксона, пока я учусь?

Извинения за многословный вопрос и спасибо заранее.


Редактировать: По предложению Шута ниже я отключил ASLR и теперь программа работает правильно. В качестве последующего вопроса, есть ли у кого-нибудь рекомендации для понимания ASLR? Ура!

1 Ответ

4 голосов
/ 23 апреля 2020

После выполнения строки system(command) система поместит в стек sh новый кадр стека для основной функции notesearch в

Нет. Это совершенно неправильно. system(xxx) является удобной библиотечной оболочкой для системного вызова execve, который сначала fork запускает процесс как дочерний:

system("xxx");

// Roughly equivalent to:

int wstatus;
pid_t child = fork();

if (child == -1) {
    return -1;
} else if (child == 0) {
    execve("/bin/sh", ["/bin/sh", "-c", "xxx"], envp); // execute shell in child
} else {
    waitpid(child, &wstatus, 0); // wait for child to complete in parent
    return WEXITSTATUS(wstatus);
}

Запускает новую оболочку, которая выполняет программу (или команда [ы]) вы передаете в качестве аргумента. Когда вы делаете это, новый дочерний элемент, равный родительскому, создается с помощью fork, а затем на дочернем элементе программа стирает из операционной системы и заменяется новым на execve. Создается новый стек и запускается новая программа.

как system() взаимодействует со стеком?

Он никак не взаимодействует, это просто обычная библиотечная функция, как я сказал выше. Когда системный вызов execve выполняется, разветвленный клон этого процесса заменяется ядром с недавно инициализированным процессом, имеющим собственное виртуальное адресное пространство (для него ASLR выполняется отдельно). Затем wait s для этого процесса оболочки, чтобы выйти. Ничто из этого не влияет на адресное пространство процесса, вызвавшего system().

, сделано ли это в x64 linux иначе, чем в x86, или я действительно неправильно понял код, написанный Эриксоном?

Вы определенно неправильно поняли код. Что делает system(), так это просто запускает уязвимую программу с точно созданным argv[1], чтобы вызвать переполнение буфера и перезаписать адрес возврата функции main(), что приводит к перезаписи RIP и контролю выполнения.

Похоже, что вызов system() изменяет стек действительно непредсказуемым образом, иногда помещая новый кадр стека ниже старого, а иногда - выше.

Конечно, поскольку system() просто создает новый процесс с execve.

, есть ли способ отключить эти меры безопасности на x64 linux при компиляции, чтобы я мог следовать коду Эриксона, пока Я учусь?

Да, вы можете отключить ASLR , чтобы ядро ​​не рандомизировало положение стека:

sudo sysctl -w kernel.randomize_va_space=0

gdb уже должен сделать это для вас, но только если процесс запускается изнутри gdb.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...