Skip to content

Нефть и монстры

Vladimir edited this page May 19, 2018 · 5 revisions
  • Категория: Pwn
  • Стоимость: 450
  • Автор: Владимир Черепанов
  • Репозиторий

Описание

А раньше всё было хорошо. Не было этих безумных монстров, от которых с каждым днём всё труднее скрыться. В воздухе не витал незаметный запах радиации, а на небе, да-да, на этом самом небе, всегда светило яркое солнце.

Ещё до катастрофы один мой знакомый владел парочкой нефтяных скважин, которые он случайно обнаружил, когда строил свою ферму. Он жутко обрадовался, конечно, и я бы обрадовался при таком раскладе-то. Ну так вот, он был уверен, что найдёт ещё скважины, поэтому написал программу, чтобы всегда знать, сколько нефти на какой скважине у него имеется.

С нефтью ещё ладно, куда ни шло, а вот программистом он был, мягко сказать, нехорошим. Рассказывали, что через эту самую его программу можно как-то управлять скважинами, и даже получалось у кого-то. Сейчас эти скважины даром никому не сдались, зато на его сервере можно найти кое-что полезное...

Чтобы получить флаг, нужно запустить getflag и передать ему токен первым аргументом.

Ваш токен: 31b65c888ea77fbbd200c518041150ab

Пример: ./getflag 31b65c888ea77fbbd200c518041150ab

nc oil.contest.qctf.ru 20001

oil

oil.c

Что даётся

  • бинарник и его исходный код на языке C
  • адрес сервера с портом, который слушает бинарник

Решение

!!! Рекомендуется сначала почитать этот разбор, так будет проще понять ход решения.

Welcome to Oil Platform Manager!

Select your action:
1. Show current oil reserve on the platform.
2. Update information about platform.
3. Export all information.
4. Exit.

Your choice: 

Вот так приветствует нас программа, которую нам предстоит эксплуатировать для получения флага.

Авторы дали нам исходник, давайте же почитаем его! В начале зачем-то написана команда для компиляции:

=== HOW 2 COMPILE ===
    
gcc -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack -m32 oil.c -o oil
    
=== OK NOW USE IT === 

Как-то многовато флагов... Должно быть, это очень уязвимый бинарник.

  • -fno-stack-protector отключает так называемые canaries - защиту стека от перезаписи (при виде такого сразу вспоминаем о переполнении буфера)
  • -z execstack помечает стек как исполняемый - прямая дорога к шеллкоду
  • -m32 компилирует бинарник под 32-битные системы (значит, внутри него 4-байтные указатели)
  • -mpreferred-stack-boundary=2 выравнивает стек по 4-м байтам (по умолчанию стек выравнивается по 16 байтам)

Бинарник позволяет нам хранить информацию о нефтяных вышках, а именно: сколько нефти есть на какой-либо вышке. Диапазон хранимого значения - один байт (беззнаковое число от 0 до 255). Программа может вывести всю имеющуюся информацию до первого нулевого символа, то есть строку, которая начинается в начале массива.

Первая странность: при записи в массив производится неправильная проверка. То есть можно записать что-то в следующий за последним (1025-ый) элемент массива (его индекс как раз и равен 1024).

scanf("%d", &number);
if (number < 0 || number > 1024) {
    printf("Incorrect number of the platform!\n");
    continue;
}

Вторая странность: посмотрим в коде как вводится число:

scanf("%lld", &platforms[number]);

scanf считывает 64-битное число и кладёт его на место одного байта. Так как языку C без разницы, какой тип у элементов массива, к которому ведёт указатель, то мы получаем перезапись целых 8 байтов в массиве.

А что будет, если записать что-то в последнюю ячейку массива? Верно, мы перезапишем то, что лежит за ним, а именно: BP и RET (своим 8-байтовым числом мы перезаписываем два 4-байтовых указателя). Но мы не достаём до конца RET из последней ячейки массива, тут нам и помогает сломанный индекс: при записи в 1025-ю ячейку массива мы можем перезаписать RET полностью... Или нет?

Возникает проблема, при которой число, большее, чем 0x7FFFFFFF не записывается. Стоит вспомнить про знаковые числа и понять, что если записать отрицательное число, то в шестнадцатеричном виде мы получим число, начинающееся с 0xFF..., что нам и нужно.

Уже ясен алгоритм: записываем байты шеллкода в массив, перезаписываем RET на начало... Стоп. А как узнать адрес массива?

Тут нам поможет функция, печатающая массив в виде строки. Нам достаточно просто заполнить его чем-то (ненулевыми байтами), тогда мы сможем слить BP (1025-1028 байты строки). А от него уже легко посчитать смещение до нашего шеллкода.

Итоговый эксплоит можно найти в репозитории, вот краткий алгоритм его работы:

  • заполняем первые байты массива шеллкодом
  • оставшиеся байты массива заполняем ненулевым мусором (чтобы дотянуться до BP)
  • для сокращения числа запросов можно отправлять не по одному байту, а сразу по 8
  • просим программу напечатать строку, достаём оттуда BP, находим адрес начала массива
  • создаём число, которым будем перезаписывать (сдвигаем на 32 бита адрес массива, вычитаем получившееся число из 2^64-1)
  • отправляем 4 и выходим из функции manage, передавая управление на шеллкод