Ниже пеервод очередной части официальных доков про Pax.
Цель Рандомизации Распределения Адресного Пространства - внесение случайности в адреса, используемые задачей. Это приводит к тому, что целый класс атак заканчивается неудачей с измеримой вероятностью, а также позволяет выявить их, так как неудачные попытки атак скорее всего приведут к "падению" атакуемой задачи. Чтобы лучше понять идеи, стоящие за ASLR, давайте рассмотрим пример задачи и его адрсного пространства: сделаем копию /bin/cat в /tmp/cat, затем отключим все функции PaX на ней и запустим "/tmp/cat /proc/self/maps". Пометки [x] не относятся к исходному выводу задачи, будем использовать их для ссылок на различные строки при обьяснении.
[1] 08048000-0804a000 R+Xp 00000000 00:0b 812 /tmp/cat
[2] 0804a000-0804b000 RW+p 00002000 00:0b 812 /tmp/cat
[3] 40000000-40015000 R+Xp 00000000 03:07 110818 /lib/ld-2.2.5.so
[4] 40015000-40016000 RW+p 00014000 03:07 110818 /lib/ld-2.2.5.so
[5] 4001e000-40143000 R+Xp 00000000 03:07 106687 /lib/libc-2.2.5.so
[6] 40143000-40149000 RW+p 00125000 03:07 106687 /lib/libc-2.2.5.so
[7] 40149000-4014d000 RW+p 00000000 00:00 0
[8] bfffe000-c0000000 RWXp fffff000 00:00 0
Как мы видим, /tmp/cat - ELF приложение с динамической линковкой, её адресное пространство содержит несколько файловых ассоциаций.
[1] и [2] ссылаются на заружаемые ELF сегменты задачи /tmp/cat, содержащие код и данные (как инициализированные, так и не инициализированные) соответственно.
[3] и [4] представляют динамический линковщик, в то время как [5], [6] и [7] - это сегменты runtime библиотеки C ([7] и [8] остаются не инициализированными, потому что достаточно велики, чтобы поместиться в последнюю страницу [6]). [8] это стэк, который растёт вниз.
Существуют различные вариации распределения памяти, которые не отображаются этим простым примером.
Для поставленных целей все эти распределения могут быть разбиты на 3 группы:
- [1],[2] и, следующая за ними, куча, управляемая brk()
- [3]-[7] все остальные распределения, созданные mmap()
- [8], стек
Области первой и третьей группы организованы при помощи execve() и не двигаются, тогда как вторая группа может возникать и исчезать в процессе работы задачи. Так как базовые адреса, используемые для размещения каждой группы не связанны друг с другом, существует возможность применять к каждому из них различную рандомизацию.
Проследим (побочные) эффекты ASLR. Наиболее интересен эффект на тип атак, которые требуют предварительного знания некоторых адресов в атакуемой задаче, например, адрес текущего указателя стека или библиотек. Если нет способа, позволяющего раскрыть информацию об уже рандомизированном распределении адресного пространства задачи, то для атаки есть лишь один способ - предположение или перебор адресов.
Предположение используется, когда рандомизация, применяемая к задаче, всегда применяется непредсказуемо. То есть злоумышленник не знает ничего о будущей рандомизации и каждый раз имеет одинаковые шансы на удачную атаку. Перебор применяется, когда злоумышленник может что-либо узнать о будущей ранзомизации и использует это знание в своей атаке. На практике перебор применяется к ошибкам в сетевых демонах, которые вызывают fork() при каждом соединении, так как fork() сохраняет рандомизированное распределение, в противоположность execve(), которая распределение его новым. Это различие между методами атаки становится ничего не значащим, если в системе предусмотрены механизмы реагирования на аварийное завершение задач. Эти реакции могут быть прописаны на столь низком уровне, что эти два вида атак будут иметь одинаковые возможности на (не)успешное завершение.
Чтобы количественно описать вышеописанные постулаты о (не)возможности удачной атаки, рассмотрим, для начала несколько переменных:
Rs: количество бит, рандомизированных в пространстве стека,
Rm: количество бит, рандомизированных в пространстве mmap(),
Rx: количество бит, рандомизированных в главном исполняемом пространстве,
Ls: положение младшего рандомизированного бита в пространстве стека,
Lm: положение младшего рандомизированного бита в пространстве mmap(),
Lx: положение младшего рандомизированного бита в главном исполняемом пространстве,
As: количество рандомизированных бит стека, уязвлённых (угаданных или подобранных) за одну попытку атаки,
Am: количество рандомизированных бит mmap(), уязвлённых за одну попытку атаки,
Ax: количество рандомизированных бит в главном исполняемом пространстве, уязвлённых за одну попытку атаки.
Например, для i386 дествительны следующие величины Rs = 24, Rm = 16, Rx = 16, Ls = 4, Lm = 12 и Lx = 12 (то есть, в адресах стека 24 бита рандомизировано в разрядах 4-27, а более старшие и более младшие биты не заронуты рандомизацией). Количество атакуемых битов получается из того факта, что в данной ситуации может быть уязвлено более одного бита (очевидно A <= R), то есть, дублирование полезной нагрузки (непосредственно кода) в памяти множество раз может преодолеть младшие рандомизированные биты.
Вероятность успеха при совершении x попыток вычисляется по следующим формулам (для предположения и перебора соответсвенно):
(1) Pg(x) = 1 - (1 - 2^-N)^x, 0 <= x (для предположения) (2) Pb(x) = x / 2^N, 0 <= x <= 2^N (для перебора)
где N = Rs-As + Rm-Am + Rx-Ax, количество необходимых рандомизированных бит.
Ниже приведены таблицы, основанные на вышеизложенных функциях вероятности удачной атаки от количества испробованных битов за одну попытку (N) и количества попыток (x).
Pg(x)| x
-----+----------------------------------------------------------------------
N | 1 4 16 64 256 2^10 2^14 2^18 2^20 2^24 2^32 2^40 2^56 2^64
-----+----------------------------------------------------------------------
1 | 0.50 0.94 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1
2 | 0.25 0.68 0.99 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1
4 | 0.06 0.23 0.64 0.98 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1
8 | ~0 0.02 0.06 0.22 0.63 0.98 ~1 ~1 ~1 ~1 ~1 ~1 ~1 ~1
16 | ~0 ~0 ~0 ~0 ~0 0.02 0.22 0.98 ~1 ~1 ~1 ~1 ~1 ~1
24 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 0.02 0.06 0.63 ~1 ~1 ~1 ~1
32 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 0.63 ~1 ~1 ~1
40 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 0.63 ~1 ~1
56 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 0.63 ~1
Pb(x)| x
-----+----------------------------------------------------------------------
N | 1 4 16 64 256 2^10 2^14 2^18 2^20 2^24 2^32 2^40 2^56 2^64
-----+----------------------------------------------------------------------
1 | 0.50
2 | 0.25 1
4 | 0.06 0.25 1
8 | ~0 0.02 0.06 0.25 1
16 | ~0 ~0 ~0 ~0 ~0 0.02 0.25
24 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 0.02 0.06 1
32 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 1
40 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 1
56 | ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 ~0 1
Очевидно, что с точки зрения защиты, цель - сделать N как можно большим и при этом уменьшить x насколько это возможно. К сожалению, защита не может влиять на N (перерандомизация адресного пространства в режиме реального времени недостижимо, потому что часть информации, необходимой для перераспределения попросту утеряна), но зависит от квалификации злоумышленника и рода уязвимости. Уменьшение N возможно, если злоумышленник может хранить множество экземпляров полезной нагрузки (например цепочка кадров стека если NOEXEC включена, или shell-код, если выключена) в адресном пространстве атакуемой задачи. Это обычно может быть возможно при тех уязвимостях, когда злоумышленник может заполнять большие области памяти необходимыми ему данными. С превышением размером этой области памяти значения L, отвечающего этой области, все больше и больше рандомизированных бит не влияют на полезную нагрузку атаки. Например, чтобы преодолеть весь R для заданного диапазона на архитектуре i386, атакующий должен отправить 256 MB данных - задача, очень трудновыполнимая (т.к. стек обычно ограничивается пределом в 8 MB и очень редко бывает больше).