Party Cat0%

09/11/25

SROP,StackPivotiFSOP

Reptes Pwn | CyCTF 2025

Thanks for sharing!

بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

SROP, Stack Pivot i FSOP
I know your browser history
Fa temps que no ens veiem, gent! He tornat amb alguns productes frescos dels recents CyCTF Quals. Vaig poder esborrar pwn alhamdulillah, així que cobriré tots els reptes d'aquest escrit. Tot estarà en detalls amb fonts per a tot! Comencem!
Tots els reptes es poden trobar aquí.

pwn1 (481 pts)

Aquest va ser un repte clàssic FSOP (File Stream Oriented Programming). Busquem en el codi font per veure què passa.

Anàlisi del codi font

El codi descompilat mostra un programa senzill basat en menús.
Aquí teniu un desglossament de les parts interessants:
  1. load_flag(): aquesta funció llegeix la bandera de flag.txt a un buffer global. L'adreça d'aquest buffer és 0x404300, que necessitarem més endavant. (Podeu obtenir-lo des de gdb o simplement fent-hi doble clic al descompilador.)
  2. fh = fopen("/dev/null", "w"): el programa obre /dev/null i emmagatzema el punter FILE en una variable global fh. S'ha assignat una estructura FILE per gestionar aquest flux.
  3. La vulnerabilitat: hi ha una opció de menú oculta, 1337 (0x539). Quan es tria, el programa executa read(0, fh, 307). Aquest és l'error! Llegeix 307 bytes de la nostra entrada directament a la ubicació de memòria apuntada per fh. Com que fh apunta a una estructura FILE al munt, podem sobreescriure el seu contingut completament.
Aquesta és una configuració de llibre de text per a un atac FSOP. En controlar l'estructura FILE, podem manipular com es comporten les funcions d'E/S com fwrite i fflush.

Estructura del fitxer?

Aleshores... què dimonis és això?
L'estructura FILE és una representació interna que utilitza la biblioteca estàndard C (libc) per gestionar els fluxos de fitxers. Conté diversos camps que fan un seguiment de l'estat del flux, inclosos els punters de memòria intermèdia, els indicadors i els punters de funció per a les operacions d'E/S.
L'estructura té aquest aspecte (estructura simplificada i completa aquí):
Quan anomenem funcions com fwrite o fflush, es basen en la informació de l'estructura FILE per determinar com gestionar les dades.
I això és exactament el que farem, sobreescriurem l'estructura FILE per fer que fwrite llegeixi des de la ubicació de memòria de la bandera i l'imprimirem. I podeu endevinar com es diu aquesta tècnica? sí, FSOP!
Cobriré només els coneixements necessaris per resoldre aquest repte, per obtenir més informació sobre l'estructura FILE, podeu cercar

Què és FSOP?

FSOP, o programació orientada a fluxos de fitxers, és una tècnica que us permet corrompre l'estructura FILE.
Si sobreescriu una estructura FILE, podem obtenir control sobre el flux d'execució del programa. Per exemple, quan es crida fflush(fh) o fwrite(..., fh), libc utilitzarà els punters de funció dins de la nostra estructura danyada, la qual cosa ens permetrà redirigir l'execució a codi arbitrari o, en aquest cas, desencadenar comportaments específics beneficiosos per a nosaltres.
Un recurs per entendre l'explotació de l'estructura FILE és pwn college, niftic.ca o qualsevol altre recurs FSOP.

El pla d'explotació

El nostre objectiu és llegir la bandera de la memòria a l'adreça 0x404300. Aquí teniu el pla pas a pas:
  1. Trieu l'opció de menú amagada 1337.
  2. Envieu una càrrega útil especialment dissenyada que sobreescriurà l'estructura FILE a la memòria.
  3. Aquesta nova estructura FILE falsa es configurarà per fer que la propera trucada fwrite ens filtri la bandera.
Aleshores, com fem que fwrite, una funció per escriure, ens llegeixi dades arbitràries?
Penseu en com funciona internament fwrite. Llegeix dades d'un buffer i les escriu a una destinació. La ubicació i la mida d'aquest buffer s'emmagatzemen dins de la pròpia estructura FILE, utilitzant punters com _IO_write_base i _IO_write_ptr.
El nostre atac és sobreescriure l'estructura FILE i canviar aquests punters. Establirem _IO_write_base perquè apunti a l'inici de la ubicació de memòria de la bandera (0x404300), _IO_write_ptr a _IO_write_base + length i fileno a 1 (stdot_6__).
fileno és el número del descriptor del fitxer, 1 correspon a la sortida estàndard (stdout). En configurar-ho, diem a fwrite que escrigui a la consola.
També hi ha una peculiaritat en què hem d'establir _IO_read_end a _IO_write_base, no sé per què, però té a veure amb la implementació de fwrite, es va esmentar a pwn.college.
pwn college
Per descomptat, la funció d'escriptura de pwntools gestiona tot això!
love pwntools
Quan el programa crida de nou a fwrite, mirarà la nostra estructura maliciosa FILE i pensarà que la memòria del senyalador és el seu buffer de dades. A continuació, procedirà a "escriure" el contingut d'aquest buffer (la bandera) a la seva destinació, que en el nostre cas és una sortida estàndard. Hem convertit amb èxit una funció d'escriptura en un gadget de lectura de memòria arbitrari!
Podeu escriure l'estructura FILE vosaltres mateixos, però pwntools ho facilita amb la seva utilitat pwnlib.filepointer.FileStructure. Simplement podem dir-li de quina adreça volem llegir i ens generarà tota la càrrega útil.

L'explotació

L'explotació final és sorprenentment curta, gràcies al poder de pwntools.
  • pay = fp.write(FLAG_ADDR, 0x100): aquest és el nucli de l'explotació. Genera una cadena de bytes que representa una estructura FILE. Aquesta estructura falsa està configurada de manera que qualsevol operació d'escriptura en ella llegeixi realment 0x100 bytes de FLAG_ADDR i els escrigui a la sortida del flux (que és stdout per defecte).
  • La read(0, fh, 307) del programa sobreescriu l'estructura FILE real amb la nostra falsa.
  • Les trucades fwrite i fflush posteriors operen a la nostra estructura maliciosa, llegeixen la bandera de la memòria i la imprimeixen.
Quin passeig! Tot i que aquest va ser el primer repte pwn, va ser el que va tenir menys solucions. Ara anem a la següent!

pwn2 (460 pts)

Aquest següent repte va ser divertit i va implicar un desbordament de memòria intermèdia que es va convertir en un atac de programació orientada a Sigreturn (SROP) per obtenir un shell.

Anàlisi del codi font

Vegem el codi descompilat.
Podeu detectar immediatament el bof a la funció check_password. Assigna un buffer de 32 bytes (local_28) a la pila, però després utilitza read per escriure-hi 0x200 (512) bytes.

El pla d'explotació

Has de saber què és el SROP i el pivotament de la pila abans de continuar, alguns bons recursos són: ir0nstone i pwn.college
En un repte SROP típic, s'utilitzarà execv amb /bin/sh, però primer necessitem una adreça que indiqui /bin/sh, això és el que no tenim aquí. Tot i que el nom d'usuari podria contenir la cadena, no hi ha cap fuga de pila per descobrir la seva adreça exacta. Així doncs, el meu pla era utilitzar read per posar la cadena /bin/sh a .bss i després fer un altre SROP per a execv.

- Gol

obtenir un shell abusant del desbordament de la pila check_password per realitzar dos fotogrames SROP encadenats:
  1. El primer marc SROP read(0, .bss, 0x200) per escriure /bin/sh i un segon marc SROP a .bss i establirà rsp a aquesta àrea.
  2. El segon marc SROP fa execve("/bin/sh", 0, 0).
Per què SROP aquí: Com que no hi ha cap gadget per configurar rdi, SROP ens permet configurar tots els registres des d'un marc sigcontext dissenyat i trucar a syscall, només un gadget per configurar rax i un Cal el gadget syscall/ret.
Per alguna raó no vaig poder fer que el primer SROP tornés directament al segon, així que vaig girar la pila.

- Flux d'alt nivell

  1. Desbordament de la memòria intermèdia password i el retorn de sobreescriure per activar un petit ROP que:
    • estableix rax en 0xF (valor d'activació sigreturn), crida al gadget syscall per invocar sigreturn.
  2. El primer fotograma de sigreturn (permet que l'anomenem fotograma A) estableix registres per trucar a read(0, bss, 0x200) i, de manera crucial, estableix rsp a bss + 0x100 (pivot de pila). rip = gadget syscall, de manera que s'executa read.
  3. Envieu la càrrega útil per a read: /bin/sh\x00 encoixinat fins a bss + 0x100, seguit d'un segon fotograma de sigreturn (fotograma B).
  4. Quan torna read, l'execució continua al rsp pivotat a .bss, ara apuntant al fotograma B; Es torna a invocar sigreturn (mitjançant el gadget syscall) per omplir els registres de execve.
  5. El marc B estableix rax=59, rdi=bss (punter a /bin/sh), rsi=0, rdx=0, rip=syscall. syscall executa execve("/bin/sh", 0, 0) -> shell.
Tots els números de syscall són per a x86_64 Linux es poden trobar aquí

L'explotació

Primer anem a triar un espai lliure d'escriptura a la memòria
vmmap
Tenim 0x4040b0.

- Truqueu a llegir (...) i pivotar la pila

Ara volem read(0, bss_addr, 0x200) i rsp = bss_addr + 0x100:

- Disseny de la segona etapa (el que enviem a llegir())

Les dades que enviem després del fotograma A (mitjançant el read) han de contenir, per ordre:
  1. la cadena b"/bin/sh\x00" (ubicada a bss_addr),
  2. farciment fins a bss_addr + 0x100,
  3. gadgets per tornar a activar sigreturn (pop rax; 0xf + syscall),
  4. quadre B (SigreturnFrame).
Concretament, en el vostre exploit, creeu second_stage com:

- Marc B: execve("/bin/sh", 0, 0)

El marc B estableix registres per a execve (syscall 59) i apunta rdi a bss_addr on s'emmagatzema /bin/sh:
L'explotació final és així:
I així, tenim una closca!

pwn3 (464 pts)

El repte final de pwn va ser un bon repte que va implicar un desbordament de memòria intermèdia per manipular els arguments d'una trucada mmap, cosa que ens va permetre executar el codi de comandament.

Anàlisi del codi font

Podeu detectar immediatament el bof a fgets(buf, 160, stdin), on buf només fa 128 bytes. Hi ha un enter mmap_prot just després d'ell a la pila, que s'utilitza com a tercer argument de mmap.

Què és mmap()?

mmap és una trucada al sistema que també es pot utilitzar per assignar regions de memòria amb permisos específics (llegir, escriure, executar). La signatura de la funció és:
Els arguments importants que hem de conèixer per a aquest repte són:
  • addr: Adreça d'inici desitjada per al mapeig.
  • length: Mida del mapeig en bytes.
  • prot: indicadors de protecció de memòria (p. ex., PROT_READ, PROT_WRITE, PROT_EXEC).

Anàlisi del codi font Cont.

mmap s'utilitza per assignar una regió de memòria a una adreça fixa 0x500000 amb mida 0x40000 i permisos definits per mmap_prot. Inicialment, mmap_prot s'estableix en 3, el que significa que la regió serà llegible i escrivible, però no executable.
Tanmateix, com que mmap_prot es troba a la pila just després del nostre buf, el podem sobreescriure amb el nostre desbordament de memòria intermèdia!

El pla d'explotació

El pla és introduir el codi shell en una regió de memòria executable i després saltar-hi.
  1. Sobreescriure mmap_prot: utilitzarem el desbordament de memòria intermèdia per canviar el valor de mmap_prot de 3 (RW-) a 7, que és 3 (RW-) a 7, (EN_MD__) PROT_READ | PROT_WRITE | PROT_EXEC. Això farà que la regió de memòria assignada per mmap sigui executable.
  2. Colocar Shellcode: el programa copia la nostra entrada de buf a la regió recentment mapejada amb memcpy. Això significa que el nostre shellcode, situat al principi de la nostra entrada, es copiarà a la regió de memòria executable.
  3. Sobreescriu l'adreça de retorn: continuarem el desbordament més enllà de mmap_prot per sobreescriure l'adreça de retorn desada amb 0x500000, l'adreça del nostre codi d'intèrpret d'ordres ara executable.

L'explotació

L'execució de l'explotació envia la càrrega útil dissenyada amb cura. El programa es desborda, mapeja una regió de memòria executable, hi copia el nostre codi d'intèrpret d'ordres i després salta directament al codi d'intèrpret d'ordres en tornar.

Això és tot per avui! Espero que us hagi agradat aquest escrit tant com jo he gaudit resolent els reptes. Pots Dox'm a X/Twitter des d'aquí. Ens veiem la propera vegada! 🍄🍄

També et pot interessar