Почему мой код C выдает ошибку сегментации, даже если указатель возврата указывает на адрес памяти для, казалось бы, корректного шелл-кода? - PullRequest
2 голосов
/ 27 июня 2019

Я пытаюсь следовать учебнику о переполнении буфера (Buffer Overflow Primer, автор Vivek Ramachandran).Я буквально следую его коду, который работает для него в демоверсии и который работал для меня до этого момента.

Цель программы на C ниже - назначить шелл-код для системного вызова exit для переменнойи затем заменить адрес возврата по умолчанию для главной функции, который указывает на __lib_start_main, адресом памяти переменной шеллкода, чтобы программа выполняла шеллкод по завершении основной функции, а затем грациозно выходила из программы с помощьюзначение 20 (как при выполнении «exit (20)»).К сожалению, программа заканчивается ошибкой сегментации.Я использую это на 32-битной Linux Mint.Я использую gcc для компиляции кода и скомпилировал его с опциями --ggdb и -mpreferred-stack-border = 2, и я пробовал как с опцией -fno-stack-protector, так и без нее.

Вот код:

#include<stdio.h>

char shellcode[] = "\xbb\x16\x00\x00\x00"
                   "\xb8\x01\x00\x00\x00"
                   "\xcd\x80";

int main(){

        int *ret;

        ret = (int *)&ret +2;

        (*ret) = (int)shellcode;

}
  1. Он начинается с определения переменной с именем shellcode, которая содержит шеллкод.
  2. вызывается основная функция и определяет переменную ret, который загружается в верхнюю часть стека
  3. Местоположение памяти переменной ret, плюс 2 целочисленных пробела, которые представляют ячейку памяти на 8 байт вниз по стеку (адрес указателя возврата)назначается в качестве значения переменной ret.
  4. Адрес памяти переменной шеллкода записывается в адрес памяти, представленный значением переменной ret, т. е. адрес возврата.
  5. Когда функция достигает инструкции возврата, она выполняет шелл-код, который является функцией выхода.

Я выполнил это через gdb, и всеКажется, проверено: Место памяти переменной шеллкода 0x804a01c

В начале выполнения main возвращаемое значение находится на третьем шестнадцатеричном слове и указывает__lib_start_main

После выполнения ret = (ret *) & ret +2 значение ret в стеке и на 8 байт больше, чем начало стека

После выполнения (* ret) = (int) шеллкода указатель возврата (3-е шестнадцатеричное слово) содержит адрес шеллкода, а не __lib_start_main

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

Заранее спасибо!

Ответы [ 2 ]

2 голосов
/ 27 июня 2019

Традиционные эксплойты переполнения буфера did требуют выполнения кода в стеке, но ваша программа этого не делает. Ваш массив shellcode не находится в стеке, и конструкция, которую вы использовали, чтобы замкнуть адрес возврата main для указания на массив shellcode, не требует выполнения кода в стеке. Когда я запускаю вашу программу на моем Linux-компьютере (также работающем на процессоре x86), скомпилированном с gcc -O0 -m32, она устанавливает регистр EIP так, чтобы он указывал на машинный код в shellcode. Но затем, как и для вас, он падает с ошибкой сегментации.

Причина сбоя в том, что shellcode загружен в область памяти, помеченную как , а не исполняемая . (Имя этой области памяти - «сегмент данных».) Процессор отказывается выполнять машинные инструкции из этой области, вместо этого генерируя «исключение» (это аппаратная концепция, а не то же самое, что исключение C ++), которое ядро переводится в сигнал SIGSEGV.

Старые учебники по написанию шелл-кода и эксплойтов переполнения буфера не предупреждают вас об этой возможности, потому что старшие поколения архитектуры x86 не могли пометить память как неисполняемую для каждой страницы. В «плоской» конфигурации сегментного регистра, используемой большинством 32-разрядных операционных систем на базе x86, любая страница, которая была читаема, также была исполняемой. Тем не менее, последние несколько поколений архитектуры смогли пометить отдельные страницы как неисполняемые, и вы должны обойти это. (Если я правильно помню, постраничная исполнимость была добавлена ​​в архитектуру x86 около 2003 года одновременно с 64-битным режимом, но для того, чтобы поддержка операционной системы стала универсальной, потребовалось немного больше времени.)

На моем компьютере с Linux, как указано выше, эта измененная версия вашей программы успешно передает управление и выполняет машинный код в shellcode. Он использует системный вызов mprotect для создания области памяти, содержащей shellcode исполняемый файл.

#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>

const char shellcode[] =
    "\xbb\x16\x00\x00\x00"
    "\xb8\x01\x00\x00\x00"
    "\xcd\x80";

int main(void)
{
  uintptr_t pagesize = sysconf(_SC_PAGESIZE);
  if (mprotect((void *)(((uintptr_t)shellcode) & ~(pagesize - 1)),
               pagesize, PROT_READ|PROT_EXEC)) {
    perror("mprotect");
    return 1;
  }

  void **ret;
  ret = (void **) &ret;
  ret[9] = (void *)shellcode;

  return 0;
}

Как и сама операция mprotect, обратите внимание, как добавление этого фрагмента кода изменило структуру стека и поместило адрес возврата в другое место. Если я скомпилирую с включенной оптимизацией, компоновка стека снова изменится, и адрес возврата не будет перезаписан. Также обратите внимание, как я сделал shellcode be const char. Если бы я этого не сделал, мне нужно было бы использовать PROT_READ|PROT_WRITE|PROT_EXEC в вызове mprotect, чтобы избежать преждевременного сбоя, потому что какая-то случайная глобальная переменная внезапно перестала быть доступной для записи, когда библиотека C ожидала этого, и ядро возможно, произошел сбой вызова mprotect из-за политики безопасности W ^ X .

В зависимости от возраста вашего ядра и библиотеки C, сделать shellcode be const char может быть достаточно само по себе, но с ядром 4.19 и glibc 2.28, что у меня есть, данные только для чтения не исполняемый либо.

0 голосов
/ 27 июня 2019

Добавление следующей опции при компиляции решило проблему:

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