Создание переполнения буфера в снежном барсе

В рамках курса по компьютерной безопасности в университете я скоро узнаю о переполнении буфера и о том, как его использовать в качестве эксплойтов. Я пытаюсь сделать простое переполнение буфера с помощью следующего кода:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char buffer_one[4], buffer_two[16];

    strcpy(buffer_one, "one");
    strcpy(buffer_two, "two");

    strcpy(buffer_one, argv[1]);

    printf("buffer_two is at %p and contains \'%s\'\n", buffer_two, buffer_two);
    printf("buffer_one is at %p and contains \'%s\'\n", buffer_one, buffer_one);
}

Я могу перезаписать содержимое buffer_one с нулевым терминатором, если я запускаю

$./overflow 1234567890123456
 buffer_two is at 0x7fff5fbff8d0 and contains '1234567890123456'
 buffer_one is at 0x7fff5fbff8e0 and contains ''

Но если я отправлю более 16 символов в качестве аргумента, программа отправит прерывание прерывания. Я предположил, что это какая-то защита от буфера на Snow Leopard (возможно, ASLR?). Если если сделать размер buffer_two < 16, адрес по-прежнему 16 бит

я бегу gcc -o overflow overflow.c -fno-stack-protector снять защиту стека

Есть ли какое-либо решение этой проблемы, кроме установки виртуальной машины под управлением дистрибутива Linux?

4 ответа

Ключ к тому, почему это происходит, заключается в том, что buffer_one расположен после buffer_two в памяти. Это означает, что при переполнении buffer_oneвы не переполняете buffer_two, Вместо этого вы переполняете память стека, используемую для хранения других вещей, таких как сохраненные ebp указатель и, самое главное, обратный адрес.

И это именно то, что вы хотите сделать при попытке использовать эксплойт переполнения буфера! Когда программа выполняется strcpy(buffer_one, argv[1]); первые четыре байта из argv[1] войти в память, выделенную для buffer_one, Но затем следующие 12 начинают переполняться памятью, используемой для других целей, в конечном итоге перезаписывая адрес возврата. Не видя машинного кода, я не могу точно сказать, какие именно байты переполняют адрес возврата. Но я предполагаю, что значение EIP во время SIGABRT составляет 0x31323334 или что-то подобное (шестнадцатеричное представление "1234"). Ключ к пониманию того, что, перезаписывая адрес возврата, вы управляете EIP. И когда вы контролируете EIP, вы контролируете систему. (несколько преувеличено, но в большинстве случаев не за горами) Когда вы управляете EIP, вы контролируете, какие инструкции процессор будет выполнять дальше (оставляя в стороне тот факт, что ОС / ядро ​​фактически стоит между ними).

Теперь, если вы точно найдете, какие восемь байтов перезаписывают адрес возврата, вы можете заменить эти байты адресом вашего буфера (0x00007fff5fbff8e0) и вместо возврата к исходному вызову (в данном случае к libc) программа начнет выполнять предоставленные вами инструкции (AKA - шеллкод). Обратите внимание, что вам нужно будет заполнить подразумеваемые 0 в наиболее значимых местах и ​​указать адрес как фактические непечатаемые символы ASCII (0x00 0x00 0x7f 0xff 0x5f и так далее), а не фактические цифры / символы 7ff5 и т. д. Если вы используете архитектуру x86-64, вам также нужно будет учитывать порядок байтов и указывать его в обратном порядке - 0xe0 0xf8 0xbf и т. д. Предоставление этих непечатаемых символов легче всего выполнить с помощью обратных кавычек и подстановки команд с помощью краткого сценария Python или Perl:

./overflow `python -c 'print "AAAAAAAAAAAAAAAA\xe0\xf8\xbf\x5f\xff\x7f"'`

(А дополняют, чтобы переполнить буфер.) К сожалению, вы не сможете предоставить 2 дополнительных \x00 нужен для адреса. Один из этих NULL будет помещен для вас strcpy, но вам придется повезти с последним NULL и надеяться, что перезаписываемый адрес уже начался 0x00 (что на самом деле очень вероятно). Теперь, когда вы выполните это с правильным числом A, вы, вероятно, по-прежнему получите ошибку сегментации или даже, возможно, недопустимую инструкцию, поскольку теперь вы перейдете к заглавной буквы A и выполните их как фактические машинные инструкции (0x41 => inc ecx).

Затем, наконец, последний кусок вставляет фактический шелл-код. Учитывая ваш ограниченный размер буфера, будет очень трудно предоставить что-нибудь полезное всего лишь в 12 байтах или около того. Поскольку в этом случае вы пишете код, возможно, проще всего будет увеличить буфер. Если это не вариант, то вы можете либо A) использовать buffer_two а также еще на 16 байт, так как он до buffer_one или B) предоставить шелл-код в переменной окружения и перейти к нему вместо этого.

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

Это технически единственные части, которые вам нужны, тем более что у вас есть четкое представление о том, каким будет адрес. Тем не менее, часто (особенно когда адрес шелл-кода неизвестен), так называемые салазки NOP будут помещаться перед шелл-кодом, так что вам не нужно будет точно определять адрес. Сани NOP (сокращение от No Operation) - это просто от сотен до тысяч инструкций NOP (0x90), которые вы можете перейти в середину и затем не иметь никакого эффекта, пока выполнение не продолжится в шеллкоде.

Если вы отслеживаете все в GDB, и выполнение правильно переходит к шелл-коду, но вы по-прежнему получаете нарушения доступа, это, вероятно, связано с тем, что на странице стека установлен бит NX, что означает, что процессор откажется выполнять данные из стека в качестве инструкций. Я не уверен, если execstack включен в OSX или нет, но если это так, вы можете использовать его в целях тестирования, чтобы отключить бит NX (execstack -s overflow).

Я извиняюсь за стену текста, но я не был уверен, как далеко вы хотите пойти, изучая переполнения буфера. Есть и другие руководства, которые вы можете проверить, такие как архетипическое руководство Aleph One "Разбить стек ради удовольствия и прибыли". Руководство Shellcoder's - хорошая книга, чтобы проверить, и я уверен, что другие могут добавить рекомендации.

TL; DR: Короче говоря, вы переполняете буфер и перезаписываете сохраненные указатели и адреса возврата мусором.

Если вы узнаете об эксплойтах, вам нужно действительно углубиться в детали.

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

Проблема может быть проще, чем это тоже. Там нет правила, которое должен поставить компилятор buffer_one а также buffer_two в любом конкретном порядке в стеке или даже положить их в стеке вообще. Заметить, что buffer_one на самом деле вписался бы в реестр.

Конечно, это не так, но я вижу, что buffer_two помещается перед buffer_one. Это означает, что запись переполнения в buffer_one никогда не будет писать в buffer_two, Я не могу объяснить, почему это в конечном итоге содержит '', но f8d0 определенно раньше f8e0 в памяти.

Данные в стеке на x86 выровнены по 4 байта. Между отступами buffer_two а также buffer_one если buffer_two длина не кратна 4 байтам. измените его на 12 или меньше, и они должны быть 12 байтов друг от друга и т. д.

[Обновление] Я пропустил размер адреса. Вы работаете в 64-битной системе, ваш стек выровнен по 8 байтов. Различия в адресах не изменятся, пока размер вашего буфера не изменится как минимум на 8 байт.

Правильна ли эта строка:

strcpy(buffer_one, argv[1]);

Вывод выглядит так, как будто вы копируете argv[1] в buffer_two,

Учитывая этот случай, сколько вы копируете, когда он падает? 17 байт? 18? Если оно больше 24, вы начнете стучать в стек таким образом, что это приведет к прерыванию.

Обратите внимание, что "1234567890123456" на самом деле копирует 17 байт, который включает в себя усечение нулевого терминатора buffer_one,

Вы пытались отключить FORTIFY_SOURCE при компиляции?

-D_FORTIFY_SOURCE=0

Другие вопросы по тегам