Создание переполнения буфера в снежном барсе - PullRequest
9 голосов
/ 13 апреля 2011

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

#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 бит </p>

Я использую gcc -o overflow overflow.c -fno-stack-protector для снятия защиты стека

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

Ответы [ 4 ]

3 голосов
/ 14 апреля 2011

Ключ к тому, почему это происходит, заключается в том, что 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, «Разбивая стек ради удовольствия и прибыли» . Справочник по кодировщику оболочек - хорошая книга для ознакомления, и я уверен, что другие могут добавить рекомендации.

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

1 голос
/ 15 июля 2014

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

-D_FORTIFY_SOURCE=0

1 голос
/ 14 апреля 2011

Данные в стеке на 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.

1 голос
/ 14 апреля 2011

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

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

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

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

...