Содержание статьи
- В королевстве PWN
- Ликбез по срыву стека на x86-64
- Стенд
- Вооружение GDB
- Кейс 1. Классический срыв стека
- Некоторые изменения в логике x86-64
- Кейс 2. Return-to-libc
- Защита от ROP
- Pwntools
- Кейс 3. Return-to-PLT
- PLT и GOT
- Что, если версия libc неизвестна?
- Bitterman
- Статический анализ
- Динамический анализ
- Темная магия pwntools
В королевстве PWN
В этом цикле статей мы изучаем разные аспекты атак типа «переполнение стека». Читай также:
Ликбез по срыву стека на x86-64
Я составил целых три импровизированных кейса, поочередно изучив которые ты получишь необходимые знания для PWN’а бинарника Bitterman.
Первый кейс покажет отличия эксплуатации Stack Smashing от этой же атаки в 32-битной ОС (о которой мы говорили в первой части цикла) в случае, когда у нарушителя есть возможность разместить и выполнить шелл-код в адресном пространстве стека, — то есть с отключенными защитами DEP/NX и ASLR.
Второй кейс поможет разобраться в проведении атаки ret2libc на x86-64 (ее 32-битный аналог был рассмотрен во второй части). Здесь мы обсудим, какие регистры использует 64-битный ассемблер Linux при формировании стековых кадров, а также посмотрим, что собой представляет концепция Return-oriented programming (ROP). Механизм DEP/NX активен, ASLR — нет.
В третьем кейсе я покажу вариацию ROP-атаки, цель которой — стриггерить утечку адреса загрузки разделяемой библиотеки libc (методика Return-to-PLT, или ret2plt) для обхода ASLR без необходимости запускать перебор. DEP/NX и ASLR активны.
От последнего этапа мы перейдем непосредственно к исследованию Bitterman, который к этому моменту уже не будет представлять для тебя сложности.
Стенд
Для этой статьи я установил свежую 64-битную Ubuntu 19.10 с GCC версии 8.3.0.
$
uname -a
Linux pwn-3 5.0.0-31-generic #33-Ubuntu SMP Mon Sep 30 18:51:59 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
Из дополнительного ПО я взял интерпретатор Python 2.7, который перестали поставлять по умолчанию с дистрибутивом (все переходят на третью версию Python).
$
sudo apt install python2.7 -y
$
sudo update-alternatives --install /usr/bin/python2 python2 /usr/bin/python2.7 1
Вторая версия пригодится нам для модуля pwntools, который мы поставим чуть позже.
Вооружение GDB
В прошлых статьях мы использовали PEDA в качестве основного обвеса для дебаггера, однако я знал, что уже есть более продвинутые тулзы для апгрейда GDB (к тому же PEDA больше не поддерживается разработчиком), а именно GEF и pwndbg. Изучая эти инструменты, я нашел изобретательный пост, в котором рассказывается, как одновременно установить эти софтины и переключаться между ними одним нажатием. Мне понравилась идея, но не реализация, поэтому я набросал свой скрипт, позволяющий в одно действие инсталлировать все три ассистента. Теперь каждый из них будет запускаться следующими командами соответственно.
$ gdb-peda [ELF-файл]
$ gdb-gef [ELF-файл]
$ gdb-pwndbg [ELF-файл]
Для этой статьи мы продолжим юзать PEDA, потому что с ним удобнее всего делать скриншоты.
Кейс 1. Классический срыв стека
Уязвимый исходный код.
/**
* Buffer Overflow (64-bit). Case 1: Classic Stack Smashing
* Compile: gcc -g -fno-stack-protector -z execstack -no-pie -o classic classic.c
* ASLR: Off (sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space')
*/
#include <stdio.h>
void vuln() {
char buffer[100];
gets(buffer);
}
int main(int argc, char* argv[]) {
puts("Buffer Overflow (64-bit). Case 1: Classic Stack Smashing\n");
vuln();
return 0;
}
В наших изысканиях всему виной будет функция vuln
, содержащая вызов уязвимой процедуры чтения из буфера gets
, которая уже стала эталоном небезопасного кода.
Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store characters past the end of the buffer, it is extremely dangerous to use. It has been used to break computer security. Use fgets() instead.
Как видишь, даже man
кричит о том, что ни в каких случаях не следует использовать gets
, ведь этой функции наплевать на размер переданного ей буфера — она прочитает из него все, пока содержимое не кончится.
Скомпилируем программу без запрета исполнения данных в стеке и отключим ASLR.
$
gcc -g -fno-stack-protector -z execstack -no-pie -o classic classic.c
$
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
Получив порцию негодования от GCC из-за использования gets
, мы собрали 64-битный исполняемый файл classic
.
Скрипт checksec.py
, идущий в комплекте с модулем pwntools и доступный из командной строки, говорит о том, что бинарь никак не защищен. Это нам и нужно для демонстрации первого кейса.
Запустим отладчик и попробуем получить контроль над регистром RIP (он отвечает за хранение адреса возврата) в момент завершения работы функции vuln
.
Некоторые изменения в логике x86-64
Регистры процессора:
- все регистры общего назначения расширены до 64 бит:
EAX->RAX
,EBX->RBX
,ECX->RCX
,EDX->RDX
,ESI->RSI
,EDI->RDI
,EBP->RBP
(база стекового кадра),ESP->RSP
(вершина стека); - введено восемь дополнительных регистров общего назначения:
R8..R15
; - служебный регистр — указатель на текущую исполняемую команду также расширен до 64 бит:
EIP->RIP
.
Память:
- размер указателя стал равен восьми байтам;
- инструкции работы со стеком
push
иpop
оперируют значениями размером восемь байт; - каноническая форма адреса виртуальной памяти имеет вид
0x00007FFFFFFFFFFF
(то есть, в сущности, используются только шесть наименьших значащих байт).
Функции:
- аргументы для функций теперь размещаются в регистрах и в стеке. Первые шесть аргументов подаются через регистры в порядке
RDI, RSI, RDX, RCX, R8, R9
, последующие помещаются в стек.
Хорошее чтиво по теме: What happened when it goes to 64 bit?
Proof-of-concept
Как обычно, будем пользоваться pattern create
, чтобы сгенерировать циклический паттерн де Брёйна, который мы скормим программе.
Этим действием, как и планировалось, мы вышли за границы отведенного буфера.
Однако несмотря на то, что отрывки нашего паттерна можно наблюдать на стеке (синий), адрес возврата (красный) перезаписать не удалось. Всему виной каноническая форма виртуальной адресации (0x00007FFFFFFFFFFF
), где задействованы лишь младшие 48 бит (6 байт). В том случае, если процессор видит «неканонический» адрес (в котором первые два значащих байта отличны от нуля), будет вызвано исключение, и контроля над RIP мы точно не получим.
Чтобы перезапись удалась, посмотрим, что находится в RSP, и посчитаем смещение.
Нам нужно 120 байт, чтобы добраться до RIP. Исходя из этого, напишем небольшой PoC-скрипт на Python, демонстрирующий возможность перезаписи адреса возврата.
#!/usr/bin/env python2
## -*- coding: utf-8 -*-
## Использование: python pwn-classic-poc.py
import struct
def little_endian(num):
"""Упаковка адреса в формат little-endian (x64)."""
return struct.pack('<Q', num)
junk = 'A' * 120
ret_addr = little_endian(0xd34dc0d3)
payload = junk + ret_addr
with open('payload.bin', 'wb') as f:
f.write(payload)
Квалификатор <Q
упакует нужный адрес в 64-битный формат little-endian.
Таким образом, RIP поддается для перезаписи произвольным значением.
Боевой пейлоад
Чтобы не мучиться с вычислением адреса загрузки шелл-кода в стеке, воспользуемся техникой размещения полезной нагрузки в переменной окружения.
Идея вкратце: адрес любой переменной окружения может быть найден с помощью простой программы на C (функция getenv
), следовательно, если разместить в такой переменной шелл-код, то можно точно узнать его адрес, что избавляет хакера от необходимости возиться с NOP-срезами. Интересно то, что на расположение шелл-кода относительно стекового пространства программы влияет ее имя.
INFO
Подробнее об этой технике читай в книге Hacking: The Art of Exploitation, PDF, с. 142.
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»