Comments 11
Принцип эксплуатации этого бородатого бага основан на увеличении объема стека без записи в него. Это делается по-разному в разных ОС, например, с помощью рекурсивного вызова процедуры, или многомегабайтными аргументами командной строки. Указатель стека перемещается на его начало (нижний адрес), стек резко наращивается, и – хоп, – указатель стека оказывается уже за сторожевой страницей. Доступа к самой сторожевой странице не происходит, ошибки нет. Получается, что область стека перекрывается с динамической областью какого-то процесса.
Вот тут уже можно пошалить. Данные из стека перезаписывают содержимое указателя из чужого процесса, либо наоборот – наш процесс перезаписывает один из чужих возвратных указателей, содержащихся в стеке. Так или иначе, это потенциально позволяет запустить свой код с повышенными правами.
У вас здесь написана ерунда, потому что вы смешали в кучу пользовательский и ядерный стек.
Смотрите: сторожевая страница находится на границе пользовательского стека.
Указатель стека перемещается на его начало (нижний адрес), стек резко наращивается, и – хоп, – указатель стека оказывается уже за сторожевой страницей. Доступа к самой сторожевой странице не происходит, ошибки нет. Получается, что область стека перекрывается с динамической областью какого-то процесса.
Здесь речь идёт о переносе указателя стека через сторожевую страницу в пользовательском процессе. В одном пользовательском процессе. По ту сторону сторожевой страницы находится виртуальная память этого же самого процесса, не какого-нибудь другого.
Вот тут уже можно пошалить. Данные из стека перезаписывают содержимое указателя из чужого процесса, либо наоборот – наш процесс перезаписывает один из чужих возвратных указателей, содержащихся в стеке. Так или иначе, это потенциально позволяет запустить свой код с повышенными правами.
Пошалить в пределах своего процесса можно было всегда, но повлиять на какой-нибудь чужой процесс так нельзя.
В статьях на которые приведены ссылки идёт речь о переполнении ядерного стека.
Честно говоря, странно видеть, что уязвимость перезаписи кучи стеком (или наоборот) до сих пор существует на 64-битных системах. Что мешает использовать для кучи и для стека адреса, разнесенные на сто тысяч гигабайт (условно)? Ядро же живет себе в верхней половине адресного пространства и его это не беспокоит.
В Windows изначально под esp нет физических страниц памяти, и чтобы туда замапилась очередная страница, нужно сделать чтение по адресу под последней выделенной страницей (и не ниже).
Например, если на стеке 10кб локальных переменных, MSVC в начале функции выполняет два холостых чтения из
Поэтому в Windows компиляторы вынуждены увеличивать стек постранично, не имея возможность пропустить guard page. Интересно, как работает стек в Linux.
Например, если на стеке 10кб локальных переменных, MSVC в начале функции выполняет два холостых чтения из
[esp-0x1000]
и [esp-0x2000]
, и это не для защиты от уязвимостей, а так работает расширение стека в Windows.Поэтому в Windows компиляторы вынуждены увеличивать стек постранично, не имея возможность пропустить guard page. Интересно, как работает стек в Linux.
Так же.
Тогда почему esp проскакивает guard page?
Для ядра компилятор генерирует код по-другому?
Для ядра компилятор генерирует код по-другому?
Насколько я понимаю, в стеке ядра нет защитных страниц.
mayorovp не так же. Вот кусок обработчика страничного сбоя для linux x86:
В переводе на русский: для сбойного адреса ищется первая VMA, конечный адрес которой больше чем адрес сбоя. Если такая VMA найдена, и она расширяется вниз (VM_GROWSDOWN) — то это область стека. Попытаться её расширить функцией expand_stack.
Т.е. сбой по любому адресу в пустом месте перед стековой VMA вызовет попытку расширения стека.
Это не о ядерном коде вообще. Ядерные стеки имеют фиксированный размер и никогда не расширяются.
vma = find_vma(mm, address);
if (unlikely(!vma)) {
bad_area(regs, error_code, address);
return;
}
if (likely(vma->vm_start <= address))
goto good_area;
if (unlikely(!(vma->vm_flags & VM_GROWSDOWN))) {
bad_area(regs, error_code, address);
return;
}
if (error_code & PF_USER) {
/*
* Accessing the stack below %sp is always a bug.
* The large cushion allows instructions like enter
* and pusha to work. ("enter $65535, $31" pushes
* 32 pointers and then decrements %sp by 65535.)
*/
if (unlikely(address + 65536 + 32 * sizeof(unsigned long) < regs->sp)) {
bad_area(regs, error_code, address);
return;
}
}
if (unlikely(expand_stack(vma, address))) {
bad_area(regs, error_code, address);
return;
}
В переводе на русский: для сбойного адреса ищется первая VMA, конечный адрес которой больше чем адрес сбоя. Если такая VMA найдена, и она расширяется вниз (VM_GROWSDOWN) — то это область стека. Попытаться её расширить функцией expand_stack.
Т.е. сбой по любому адресу в пустом месте перед стековой VMA вызовет попытку расширения стека.
Для ядра компилятор генерирует код по-другому?
Это не о ядерном коде вообще. Ядерные стеки имеют фиксированный размер и никогда не расширяются.
Это не о ядерном коде вообще. Ядерные стеки имеют фиксированный размер и никогда не расширяются.
Вот именно, об этом я и говорил.
jcmvbkbc, теперь понятно.
Linux в usermode может расширять стек не на 1 страницу, а сразу намного (но не более 64KB за 1 раз, наверное чтобы случайное чтение на 15 гигов ниже стека не скушало всю физическую память под стек).
Linux в usermode может расширять стек не на 1 страницу, а сразу намного (но не более 64KB за 1 раз, наверное чтобы случайное чтение на 15 гигов ниже стека не скушало всю физическую память под стек).
но не более 64KB за 1 раз
Это вы из проверки внутри if (error_code & PF_USER) сделали вывод? Но стек необязательно двигать командой entry. Можно тупо sub esp, 100500. Эта проверка о том, что обращаться к стеку ниже текущего указателя стека вообще-то нельзя, но если надо — то можно, но недалеко (64К).
Sign up to leave a comment.
Security Week 25: В *NIX реанимировали древнюю уязвимость, WannaCry оказался не доделан, ЦРУ прослушивает наши роутеры