415 lines
19 KiB
Markdown
415 lines
19 KiB
Markdown
|
|
# food / fooc — et stack-bufferoverløp, fra begge sider
|
||
|
|
|
||
|
|
Et C99-sikkerhetslaboratorium i to halvdeler:
|
||
|
|
|
||
|
|
- **`food.c`** — en bevisst sårbar TCP-daemon. Den har et ekte,
|
||
|
|
lærebokaktig stack-bufferoverløp (CWE-120), og noen flere feil i tillegg.
|
||
|
|
- **`fooc.c`** — et exploit for det. Det beregner overflow-offsetet ved å
|
||
|
|
disassemblere målprogrammet under kjøring, leser adresse-leaks fra daemonen,
|
||
|
|
og får en shell på "offeret" ved å overskrive en lagret returadresse.
|
||
|
|
|
||
|
|
Poenget er ikke shellen. Poenget er at du kan følge med fra ende til annen
|
||
|
|
hvordan en minnesikkerhetsfeil blir til vilkårlig kodeutførelse — og deretter
|
||
|
|
se nøyaktig hvilke mottiltak som stopper hvert trinn i den kjeden. Hver linje i
|
||
|
|
begge programmene er kommentert, fordi mekanismen er leksjonen.
|
||
|
|
|
||
|
|
```
|
||
|
|
terminalen din
|
||
|
|
|
|
||
|
|
./fooc (exploit)
|
||
|
|
|
|
||
|
|
TCP 127.0.0.1:2342
|
||
|
|
|
|
||
|
|
./food (sårbar daemon)
|
||
|
|
|
|
||
|
|
fork() -> vulnerable_handler() -> overflow -> ret -> koden din
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## ⚠️ Les dette først
|
||
|
|
|
||
|
|
**`food` er en bevisst ødelagt nettverkstjeneste. Den binder bare til
|
||
|
|
`127.0.0.1`, og den standarden er bevisst — la den være der.**
|
||
|
|
|
||
|
|
- Kjør den **ikke** på en maskin du bryr deg om, eller på noe med data på.
|
||
|
|
- Bind den **ikke** til `0.0.0.0` eller en ekte nettverksgrensesnitt. Den er
|
||
|
|
bevisst eksternt utnyttbar.
|
||
|
|
- Å peke `fooc` mot en vert du ikke eier eller ikke har skriftlig tillatelse
|
||
|
|
til å teste, er en datainnbruddsforseelse i de fleste jurisdiksjoner — også
|
||
|
|
etter UK Computer Misuse Act og US Computer Fraud and Abuse Act.
|
||
|
|
- Den binder til en uprivilegert port (>1024), så du trenger ikke root. Ikke
|
||
|
|
"forbedre" den ved å legge til capabilities eller kjøre den som
|
||
|
|
systemtjeneste.
|
||
|
|
- Hver tilkobling håndteres i et `fork()`et barn, og `food` reaper det, så
|
||
|
|
krasj hoper seg ikke opp. Hvis du etterpå finner dusinvis av løse
|
||
|
|
`sh`-prosesser, er `pkill -x sh` oppryddingen.
|
||
|
|
|
||
|
|
I tvilstilfeller: Dette laboratoriet er for en virtuell maskin eller en
|
||
|
|
container, på et nettverk du kontrollerer, på en maskin uten noe du ville
|
||
|
|
savnet.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Rask start
|
||
|
|
|
||
|
|
```sh
|
||
|
|
make # bygger food, fooc og test-harnessene
|
||
|
|
make run # starter food på 127.0.0.1:2342, løsrevet i bakgrunnen
|
||
|
|
make test # kjører alle tre exploit-teknikkene
|
||
|
|
make stop # stopper daemonen
|
||
|
|
```
|
||
|
|
|
||
|
|
Deretter for hånd:
|
||
|
|
|
||
|
|
```sh
|
||
|
|
./fooc -t leak # se adresse-leaksene food gir fra seg
|
||
|
|
./fooc -t demo -v # send søppel; se food dø med SIGSEGV
|
||
|
|
./fooc -t ret2win -i # hopp til en funksjon som allerede finnes -> shell
|
||
|
|
```
|
||
|
|
|
||
|
|
### Krav
|
||
|
|
|
||
|
|
| Verktøy | Til hva | Merknader |
|
||
|
|
|---|---|---|
|
||
|
|
| `gcc` (eller clang) | bygging | C99. Testet med gcc 16.2 |
|
||
|
|
| `objdump` | `fooc` | binutils. `fooc` kaller det under kjøring |
|
||
|
|
| `nasm` | `make verify` | bare for å kryssjekke shellcoden; hoppes over hvis det mangler |
|
||
|
|
| `gdb` | `make debug` | valgfritt |
|
||
|
|
| Linux, x86-64 | begge | payload og gadget-jakt er arkitekturavhengige |
|
||
|
|
|
||
|
|
`fooc` trenger også `-ldl` for `dlsym()`; Makefile-et ordner det.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Feilen
|
||
|
|
|
||
|
|
Én linje i `food.c` er hele angrepsflaten:
|
||
|
|
|
||
|
|
```c
|
||
|
|
char buf[FOOD_BUFSZ]; /* 64 bytes */
|
||
|
|
n = read(fd, buf, FOOD_READMAX); /* opptil 512 bytes fra nettverket */
|
||
|
|
```
|
||
|
|
|
||
|
|
64 bytes destinasjon, 512 bytes akseptert. Angriperen overskriver 448 bytes
|
||
|
|
forbi slutten av bufferen, og fordi stacken vokser nedover, betyr "forbi
|
||
|
|
slutten" "inn i rammen over" — og det er akkurat der den lagrede
|
||
|
|
rammepekeren og den **lagrede returadressen** ligger.
|
||
|
|
|
||
|
|
I en kompilert x86-64-funksjon ved `-O0`:
|
||
|
|
|
||
|
|
```
|
||
|
|
høye adresser
|
||
|
|
+------------------------+ rbp + 16 : kallers lokale
|
||
|
|
| ... |
|
||
|
|
+------------------------+ rbp + 8 : LAGRET RETURADRESSE <-- blir til RIP
|
||
|
|
| saved rbp (8 bytes) |
|
||
|
|
+------------------------+ rbp : vår rammepeker
|
||
|
|
| line[128] |
|
||
|
|
| buf[64] | <- rsp: det read() fyller
|
||
|
|
+------------------------+
|
||
|
|
lave adresser
|
||
|
|
```
|
||
|
|
|
||
|
|
Når funksjonen returnerer, popper `leave; ret` de 8 bytene inn i `RIP`, og
|
||
|
|
CPU-en hopper dit angriperen har valgt. Alt annet i dette laboratoriet er
|
||
|
|
aritmetikk om hvorhen man skal peke.
|
||
|
|
|
||
|
|
For denne builden er tallene: `buf` er 64 bytes, det lagrede `rbp` er 8, så
|
||
|
|
returadressen ligger på offset **88** fra starten av `buf`. `fooc` hardkoder
|
||
|
|
ikke det — det disassemblerer `food` og finner `lea -0x50(%rbp)` foran
|
||
|
|
`call read@plt`, så det fortsatt virker hvis du endrer `FOOD_BUFSZ`.
|
||
|
|
|
||
|
|
> gcc forteller deg allerede om dette. Å bygge `food` skriver ut:
|
||
|
|
> `warning: 'read' writing 512 bytes into a region of size 64 overflows the
|
||
|
|
> destination [-Wstringop-overflow=]`. Aldri undertrykk den advarselen i ekte
|
||
|
|
> kode. Den er gratis sikkerhet.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## De tre teknikkene
|
||
|
|
|
||
|
|
`fooc -t <technique>`. De står i den rekkefølgen en ekte angriper ville
|
||
|
|
arbeidet seg gjennom dem, fordi hver enkelt trenger det den forrige lærte deg.
|
||
|
|
|
||
|
|
### 1. `ret2win` — kontroller instruksjonspekeren
|
||
|
|
|
||
|
|
```
|
||
|
|
[ 88 bytes søppel ][ adressen til food's win() ]
|
||
|
|
^ saved rbp
|
||
|
|
^ blir til RIP
|
||
|
|
```
|
||
|
|
|
||
|
|
`win()` er en funksjon i målprogrammet som exec'er `/bin/sh`. Å overskrive
|
||
|
|
returadressen med dens adresse er hele exploitet.
|
||
|
|
|
||
|
|
**Hva det lærer:** du har vilkårlig kontroll over instruksjonspekeren. Det
|
||
|
|
trenger heller ikke noe leak, fordi binærfilen er bygget `-no-pie`, så `win()`
|
||
|
|
sitter på en fast adresse for alltid.
|
||
|
|
|
||
|
|
**Den virkelige verdens ekvivalent** er ikke "angrep er enkle", men "ikke
|
||
|
|
send ut udokumenterte bakdører i nettverks-binærfiler". Hvis en funksjon som
|
||
|
|
`win()` finnes i binærfilen din, vil et bufferoverløp finne den. Det er
|
||
|
|
bokstavelig talt Juniper ScreenOS-bakdør-CVE-klassen.
|
||
|
|
|
||
|
|
**Forsvar:** `-fPIE` (eller ASLR) randomiserer lasteadressen, så angriperen må
|
||
|
|
kjenne adressen — noe som vanligvis betyr at de først trenger et leak. Derfor
|
||
|
|
feiler `ret2win` mot `food_hardened`.
|
||
|
|
|
||
|
|
### 2. `ret2libc` — kall hva som helst, ved navn
|
||
|
|
|
||
|
|
```
|
||
|
|
[ søppel ][ pop rdi; ret ][ adressen til "/bin/sh" ][ adressen til system() ]
|
||
|
|
^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
|
||
|
|
setter rdi strengen å sende funksjonen å kalle
|
||
|
|
```
|
||
|
|
|
||
|
|
Ved utførelse: `ret` popper `pop rdi; ret` inn i RIP; det popper
|
||
|
|
`"/bin/sh"`-pekeren inn i `RDI`; dets `ret` popper `system()` inn i RIP, mens
|
||
|
|
`RDI` fortsatt holder strengen. `system("/bin/sh")` kjører.
|
||
|
|
|
||
|
|
Gadgets (`pop rdi; ret`) er ikke i `food` — denne glibc-en har ikke noe
|
||
|
|
`__libc_csu_init` — så `fooc` finner dem ved å skanne live libc-minne etter
|
||
|
|
byte-paret `5f c3`. Den lokaliserer libc via `/proc/self/maps`, finner
|
||
|
|
offsets for `system` og `"/bin/sh"` med `dlsym()` og beregner basen fra
|
||
|
|
leaket `food` publiserer. Ingenting er hardkodet, så det overlever en
|
||
|
|
libc-oppdatering.
|
||
|
|
|
||
|
|
**Hva det lærer:** når du først kan kontrollere `RIP`, kan du kjede sammen
|
||
|
|
*eksisterende* instruksjoner. Dette er return-oriented programming, og det er
|
||
|
|
slik nesten alle ekte exploits ser ut, fordi det ikke krever
|
||
|
|
angriperlevert kjørbart minne.
|
||
|
|
|
||
|
|
**Forsvar:** ingen av kompilator-flagene stopper dette alene. Det virker mot
|
||
|
|
en PIE-binærfil, med NX, med canary — så lenge angriperen har et leak.
|
||
|
|
Forsvarene er "ikke ha overløpet" og "ikke lek adresser." Se tabellen nedenfor.
|
||
|
|
|
||
|
|
### 3. `shellcode` — kjør din egen maskinkode
|
||
|
|
|
||
|
|
23 bytes, plassert ved starten av bufferen, med `RIP` pekende på dem:
|
||
|
|
|
||
|
|
```asm
|
||
|
|
xor esi, esi ; envp = NULL
|
||
|
|
xor edx, edx ; argv = NULL
|
||
|
|
movabs rdi, 0x68732f6e69622f ; rdi = "/bin/sh\0" som 8 rå bytes
|
||
|
|
push rdi ; legg strengen på stacken
|
||
|
|
mov rdi, rsp ; rdi = &"/bin/sh"
|
||
|
|
push 0x3b ; 59 = __NR_execve
|
||
|
|
pop rax
|
||
|
|
syscall ; vi er nå en shell
|
||
|
|
```
|
||
|
|
|
||
|
|
Dette er den reneste formen for feilen: angriperen leverer *instruksjonene*,
|
||
|
|
ikke bare adressen til instruksjoner som allerede finnes. Ingen libc-offsets
|
||
|
|
nødvendig, så det virker i prinsippet mot et statisk linket, fullt
|
||
|
|
randomisert mål.
|
||
|
|
|
||
|
|
`make verify` assemblerer `shellcode.S` og diff'er det mot byte-arrayet
|
||
|
|
innebygd i `fooc.c`, så de to ikke kan drive fra hverandre.
|
||
|
|
|
||
|
|
**Forsvar:** **NX** (også kalt W^X, "no execute"). Å markere stacken som
|
||
|
|
ikke-kjørbar gjør at maskinvaren nekter å hente instruksjoner fra den, og
|
||
|
|
`ret`-et lander på en side som ikke kan kjøres. Det er derfor `make food`
|
||
|
|
sender `-z execstack`: en vanlig Linux-stack er `rw-p`, ikke `rwx`, og
|
||
|
|
teknikken dør med SIGSEGV ved `RIP = payloadens adresse`. Den absolutt
|
||
|
|
viktigste leksjonen i laboratoriet er at hver eneste av disse bytene bare
|
||
|
|
virker fordi kompilatoren fikk beskjed om å la stacken være kjørbar. Det
|
||
|
|
flagget er på til gagn for ingen.
|
||
|
|
|
||
|
|
### Også inkludert
|
||
|
|
|
||
|
|
| Modus | Hva den gjør |
|
||
|
|
|---|---|
|
||
|
|
| `-t leak` | kobler til, skriver ut leaks, sender ingenting |
|
||
|
|
| `-t demo` | sender `rip_off + 8` bytes `0x41`, så `RIP` blir `0x4141...` og daemonen dør. Beviser feilen helt uten adressekunnskap |
|
||
|
|
| `-t sled` | et ret-sled, bevisst beholdt som et **feilende** eksempel. Uten et leak ville du brute-force ASLR ved å fylle bufferen med adressen til et `ret`. Det kan ikke virke her: `food` aksepterer 512 bytes, så sleden har ~53 slots mot ~28 bits entropi. Implementert så du kan se det feile og bekrefte at mekanismen virkelig er "CPU-en følger en kjede av rets" |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Mottiltak-tabellen
|
||
|
|
|
||
|
|
Dette er delen å huske. Hver rad er et ekte forsvar, og høyre kolonne viser hva
|
||
|
|
den faktisk gjør med hendelseskjeden.
|
||
|
|
|
||
|
|
| Mottiltak | Slik aktiverer du | Hva den stopper | Hva den *ikke* stopper |
|
||
|
|
|---|---|---|---|
|
||
|
|
| **Begrens read** | `n = read(fd, buf, sizeof buf - 1);` | **Alt.** Feilen finnes ikke, så ingenting nedstrøms betyr noe | Ingenting — dette er den eneste fullstendige fixen |
|
||
|
|
| **Stack-canary** | `-fstack-protector-strong` (gccs standard) | `ret`-et: canaryen sjekkes ved funksjonsavslutningen, så smadringen oppdages og prosessen aborter før `RIP` poppes | En feil i en funksjon *uten* array (ingenting å beskytte); et overløp som holder seg under canaryen; alt som ikke returnerer normalt |
|
||
|
|
| **NX / W^X** | `-z noexecstack` (standarden) | Shellcode. Payloadens egne instruksjoner kan ikke hentes | ret2win og ret2libc fullstendig. Disse er *grunnen* til at ROP finnes |
|
||
|
|
| **PIE + ASLR** | `-fPIE` + ASLR=2 (begge standard) | ret2wins hardkodede adresser. Alt flytter seg ved hver kjøring | Alt der angriperen har et leak. ASLR hever prisen på et exploit; det er ikke en fix. Merk at stack, heap og mmap randomiseres, men hoved-binærfilens *innhold* gjør det ikke — det er det ROP-kjeder bruker |
|
||
|
|
| **Ikke lek** | ingen `printf("%p")` til klienter; initialiser før du skriver ut | Informasjonsleaket som gjør ASLR fra "dyrt" til "gratis" | — |
|
||
|
|
| **Ikke bruk `printf(user_data)`** | `printf("%s", buf)` i stedet for `printf(buf)` | Format-streng-feil: `%x`-stack-reads, `%n`-vilkårlige skrivninger, som er en *annen* vei til RCE | — |
|
||
|
|
| **Ikke bruk upålitelige stier** | valider og `openat()` under en fast mappe | Sti-traversal (CWE-22) | — |
|
||
|
|
| **CET / shadow stack** | `-fcf-protection=full`, kjernens og CPU-ens støtte | `ret`-et selv: shadow stacken husker den *ekte* returadressen og feiler ved mismatch. Fanger ROP-kjeder som bruker maskinvare-`ret` | Angrep som aldri `ret`-er (call-oriented, eller å overskrive en funksjonspekermål med en gadget-kjede som ikke trenger retur) |
|
||
|
|
| **Sikre språk** | Rust, Go, C# for ny kode | Hele klassen. Bounds-sjekker utføres ved kjøring, ikke håpet på ved review | — |
|
||
|
|
|
||
|
|
### Se det selv
|
||
|
|
|
||
|
|
```sh
|
||
|
|
make run # sårbar daemon
|
||
|
|
make test # alle tre teknikkene virker
|
||
|
|
|
||
|
|
make test-hardened # samme kildekode, mottiltak på
|
||
|
|
```
|
||
|
|
|
||
|
|
`test-hardened` bygger `food_hardened` med `-fstack-protector-strong -fPIE -pie
|
||
|
|
-z noexecstack`, bytter den inn, kjører alle tre igjen og legger så den
|
||
|
|
sårbare tilbake. Du vil se:
|
||
|
|
|
||
|
|
```
|
||
|
|
### stack segment: 'rw-p' (NOT executable) is what you want to see
|
||
|
|
--- ret2win was stopped by the mitigations (as expected)
|
||
|
|
--- ret2libc was stopped by the mitigations (as expected)
|
||
|
|
--- shellcode was stopped by the mitigations (as expected)
|
||
|
|
```
|
||
|
|
|
||
|
|
Og i den hardnede daemonens logg, canaryen som utløses:
|
||
|
|
|
||
|
|
```
|
||
|
|
*** stack smashing detected ***: terminated
|
||
|
|
```
|
||
|
|
|
||
|
|
Les det nøye, for det er den viktigste linjen i hele laboratoriet: **canaryen
|
||
|
|
fanget ret2win, ikke PIE.** Alle tre teknikkene dør ved canaryen, fordi alle
|
||
|
|
tre går gjennom det samme `read()` og ødelegger den samme rammen. NX stopper
|
||
|
|
bare i tillegg shellcodens *kode*; PIE bryter bare i tillegg den hardkodede
|
||
|
|
adressen. Slå dem på enkeltvis, og du vil oppdage at de fleste enkeltstående
|
||
|
|
mottiltak etterlater deg utsatt for noe.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Filer
|
||
|
|
|
||
|
|
| Fil | Formål |
|
||
|
|
|---|---|
|
||
|
|
| `food.c` | den sårbare daemonen. 6 nummererte feil, hver med sin fix i kommentaren |
|
||
|
|
| `fooc.c` | exploitet. objdump-basert offset-oppdagelse, `/proc`-basert libc-oppdagelse, 4 payload-byggere |
|
||
|
|
| `shellcode.S` | de 23 shellcode-bytende som assembly, så de kan leses og verifiseres. `fooc` bærer dem inline og trenger ikke dette ved kjøring |
|
||
|
|
| `Makefile` | bygger, tester og den hardnede sammenligningen |
|
||
|
|
| `tests/pty_test.c` | driver `fooc` gjennom et pseudo-terminal og sjekker for ekte shell-output |
|
||
|
|
| `tests/sock_test.c` | uavhengig verifikator over en rå socket, så resultatet ikke avhenger av `fooc` |
|
||
|
|
| `food.log` | daemonens logg. Beviset ditt på hva som skjedde |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## To feil i dette laboratoriet som er verdt å forstå
|
||
|
|
|
||
|
|
Dette er ikke målprogrammets feil. Det er feil i exploitet og i dets
|
||
|
|
test-harness, og begge produserte overbevisende løgner. De er dokumentert i
|
||
|
|
kilden der de bor; her står de fordi sviktmønstrene er lærerike.
|
||
|
|
|
||
|
|
### Stack-justering: krasjet som ikke er en NULL-dereferanse
|
||
|
|
|
||
|
|
**Symptom.** Kapringen lander korrekt — `gdb` viser deg i `win()` — og så dør
|
||
|
|
det aller første `win()` gjør, en `dprintf()`. `SIGSEGV`-handleren rapporterer
|
||
|
|
`RIP` dypt inne i glibcs formatter og en feiladresse på `(nil)`, noe som ser
|
||
|
|
nøyaktig ut som en korrupt peker.
|
||
|
|
|
||
|
|
**Årsak.** System V AMD64-ABI-en krever 16-byte stack-justering. Et normalt
|
||
|
|
`ret` gjenoppretter `%rsp` til nøyaktig det det matchende `call` lagret, så
|
||
|
|
invarianten bevares gratis. Vårt nakne `ret` gjør ikke det: etter det gjelder
|
||
|
|
`%rsp = buf + rip_off`. Her er `buf` 16-byte justert og `rip_off` er 88, så
|
||
|
|
callee-en får en stack som er 8 mod 16. glibc er kompilert med SSE2, og
|
||
|
|
`movaps` **feiler** på et feiljustert operand. På x86 reiser det `#GP`, ikke
|
||
|
|
`#PF`, så kjernen har ingen feiladresse og rapporterer `si_addr = 0`. Det
|
||
|
|
NULL-et er fingerpeket: en justeringsfeil forkledd som en NULL-dereferanse.
|
||
|
|
|
||
|
|
**Fix.** Én `ret`-gadget *ved offset `rip_off`*, som flytter det ekte målet 8
|
||
|
|
bytes opp, siden hvert `ret` legger nøyaktig 8 til `%rsp`. Rekkefølgen er
|
||
|
|
kritisk: en tidligere versjon la `ret`-et *etter* målet og produserte
|
||
|
|
`[ padding | target | ret ]`, der det avsluttende `ret`-et aldri nås og fixen
|
||
|
|
stille og rolig ikke gjør noe. Et påfallende `ret` som ser ut som en feil, er
|
||
|
|
nesten alltid bevisst.
|
||
|
|
|
||
|
|
### Én socket, to lesere: byten som forsvant
|
||
|
|
|
||
|
|
**Symptom.** Shellcode ble rapportert som fungerende. Så ble pty-harnessen
|
||
|
|
gjort strengere (slå av `ECHO`, så terminalen sluttet å ekkoe harnessens egen
|
||
|
|
kommandolinje tilbake til seg), og teknikken begynte å feile. Under alt sammen
|
||
|
|
mistet hver teknikk nøyaktig én byte fra starten av hver utgangs-chunk:
|
||
|
|
`uid=1000(hanez)` ble skrevet ut som `id=1000(hanez)`, `PWNED-OK` som
|
||
|
|
`WNED-OK`, `Linux 7.2.7` som `inux 7.2.7`.
|
||
|
|
|
||
|
|
**Årsak.** `fooc` pleide å `dup2()`e socketen inn på sitt eget stdin/stdout
|
||
|
|
og `execv()`e en *lokal* `/bin/sh`, mens et forket relay-barn også leste den
|
||
|
|
samme socketen for å flytte utdata til terminalen. Kjernen bryr seg ikke om at
|
||
|
|
de to samarbeider. En streamsocket har **én** lese-cursor, og hver leser flytter
|
||
|
|
den, så bytes deles uforutsigbart mellom dem. Den lokale shellen, som er en
|
||
|
|
interaktiv login-shell, leste nøyaktig én byte og kastet den — hver eneste
|
||
|
|
gang. `strace -f` viste det umiddelbart:
|
||
|
|
|
||
|
|
```
|
||
|
|
read(0, "u", 1) <- den lokale shellen, spiser en byte
|
||
|
|
read(4, "id=1000(hanez) gid=1000(hanez) g".., 310) <- relayet, 1 byte for kort
|
||
|
|
```
|
||
|
|
|
||
|
|
**Fix.** Det er ingen shell på denne siden i det hele tatt. Det er nøyaktig én
|
||
|
|
shell i hele bildet, og den er på offeret, inne i den kaprede prosessen, med
|
||
|
|
TCP-tilkoblingen som sin stdin/stdout. Denne siden flytter bare bytes. Hvis du
|
||
|
|
noen gang trenger to forbrukere av en stream, trenger den streamen én eneste
|
||
|
|
leser som bevisst demultiplekser den.
|
||
|
|
|
||
|
|
**Meta-leksjonen.** Det første "fungerende" resultatet var et falskt positivt
|
||
|
|
produsert av at pty-en ekkoet harnessens egen kommandolinje tilbake til den, og
|
||
|
|
fixen for det falske positive er det som avslørte den ekte feilen. Tester som
|
||
|
|
ikke kan feile, er verre enn ingen tester, fordi de forvandler "jeg vet ikke"
|
||
|
|
til "det virker." En test-harness fortjener samme mistenksomhet som koden den
|
||
|
|
tester.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Å eksperimentere med det
|
||
|
|
|
||
|
|
Ting som er verdt å prøve, omtrent i den rekkefølgen du lærer mest av dem:
|
||
|
|
|
||
|
|
1. **Endre `FOOD_BUFSZ` til 128.** Kjør `fooc` igjen. Det burde fortsatt virke
|
||
|
|
uten endringer, fordi det leser offsetet ut av disassembly-en. Bryt det så
|
||
|
|
for hånd — hardkod 88 — og se det krasje. Legg så til et andre array mellom
|
||
|
|
`buf` og de lagrede registrene, og se den automatiske oppdagelsen håndtere
|
||
|
|
det.
|
||
|
|
|
||
|
|
2. **Legg til `-Wformat-security` og se hva format-streng-stien gjør.** Send
|
||
|
|
`%p %p %p %n` og se `food` lekke stacken.
|
||
|
|
|
||
|
|
3. **Bruk gdb.** `make debug`, deretter:
|
||
|
|
```gdb
|
||
|
|
(gdb) break food.c:393 # det read() som renner over
|
||
|
|
(gdb) run -p 2342
|
||
|
|
(gdb) info registers rsp rbp
|
||
|
|
(gdb) x/24gx $rsp # legg merke til hvor returadressen ligger
|
||
|
|
(gdb) c # i et annet terminal: ./fooc -t ret2win
|
||
|
|
```
|
||
|
|
`SIGSEGV`-handleren logger `REG_RIP` og `REG_RSP`, så `food.log` forteller
|
||
|
|
deg om kapringen landet, selv når barnet dør før du kan koble deg på.
|
||
|
|
|
||
|
|
4. **Slett justeringsfixen** i `fooc.c` og se `#GP`-feilen med
|
||
|
|
`si_addr = 0`-signaturen. Les så `/proc/sys/kernel/randomize_va_space` og
|
||
|
|
tenk over hva ASLR randomiserer, og hva det ikke gjør.
|
||
|
|
|
||
|
|
5. **Bryt libc-symboloppløsningen** og se `fooc` tilpasse seg. Hele poenget
|
||
|
|
med `/proc/self/maps`-tilnærmingen er at intet offset er hardkodet.
|
||
|
|
|
||
|
|
6. **Skriv en fjerde teknikk.** En `ret2csu`-lignende kjede hvis du kan finne
|
||
|
|
`__libc_csu_init`, eller en SROP-kjede (`sigreturn`-rammer lar deg
|
||
|
|
kontrollere alle registre på én gang). Begge er rent ROP og trenger ikke
|
||
|
|
kjørbart minne.
|
||
|
|
|
||
|
|
7. **Fix `food.c` ordentlig**, én feil om gangen, og kjør exploitet igjen etter
|
||
|
|
hver fix. Rekkefølgen i tabellen øverst i `food.c` er omtrent den riktige
|
||
|
|
rekkefølgen å tenke i: begrens først read-et, for ingenting annet betyr noe
|
||
|
|
før feilen er borte.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Opprydding
|
||
|
|
|
||
|
|
```sh
|
||
|
|
make stop # stopper food
|
||
|
|
make clean # fjerner byggeprodukter; lar food.log være i fred
|
||
|
|
pkill -x sh # bare hvis du har løse shells fra en test som gikk skeis
|
||
|
|
```
|
||
|
|
|
||
|
|
Merk at `pkill -x food` matcher prosess**navnet** nøyaktig. Ikke bruk
|
||
|
|
`pkill -f ./food` — det mønsteret matcher også shellen du skrev det i, og
|
||
|
|
dreper din egen sesjon. Det er ikke en hypotese; det skjedde mens dette ble
|
||
|
|
bygget.
|