Skip to content

IO_2_1_stdout leak

stdout FILE struct'ını corrupt et; böylece bir sonraki puts/printf bitişik libc memory'sini flush'lar ve ASLR'ı yenmek için bir libc pointer'ı leak eder.

Mechanism

Note

glibc'nin buffered write path'i (_IO_new_file_overflow_IO_do_writenew_do_write) count = _IO_write_ptr - _IO_write_base byte flush'lar. Boştaki bir stdout için bu pointer'lar eşittir, dolayısıyla count == 0 ve hiçbir şey leak olmaz. Attack bu invariant'ı _IO_2_1_stdout_ FILE struct'ını düzenleyerek bozar:

  • _flags = 0xfbad1800 = 0xfbad0000 (magic) | _IO_CURRENTLY_PUTTING (0x800) | _IO_IS_APPENDING (0x1000) ayarla. Bunlar yürütmeyi kontrolleri aşıp doğrudan write'a yönlendirir ve "buffered output var" diye iddia eder.
  • _IO_write_base'in düşük byte'(lar)ını 0x00'a overwrite et (tek bir relative null-byte write). Artık _IO_write_base < _IO_write_ptr, dolayısıyla count büyük olur ve write() düşürülmüş base'ten ileriye memory döker — pointer dolu bitişik libc yapıları boyunca.
  • _IO_read_end de sıfırlanır, böylece new_do_write offset/lseek doğrulaması (_IO_IS_APPENDING set olduğunda atlanır) abort etmez.

Invariant: FILE struct'ı flush descriptor'ının ta kendisidir. Onun write base'ini düşürmek normal bir print'i bir out-of-bounds memory dump'ına çevirir.

Warning

Offset'ler libc-build'e-özgüdür. _flags/_IO_write_base trick'i glibc 2.23, 2.27, 2.31 üzerinde gösterilmiştir; numeric leak offset'i (ör. leak - 0x3ed8b0) her libc için yeniden hesaplanmalı. Modern glibc (≥ 2.35) FILE handling'ini ve vtable kontrollerini sıkılaştırır, dolayısıyla klasik FSOP mekanikleri farklıdır.

Walkthrough

_IO_2_1_stdout_'u _flags'ten başlayarak overwrite et, sonra leak'i oku:

from pwn import *
payload  = p64(0xfbad1800)        # _flags: magic | CURRENTLY_PUTTING | IS_APPENDING
payload += p64(0)*3               # _IO_read_ptr / _IO_read_end / _IO_read_base
payload += b"\x00"                # low byte of _IO_write_base -> 0x00

# ... deliver via your write primitive onto &_IO_2_1_stdout_ ...
# next buffered output triggers the leak:
leak = u64(p.recv(6).ljust(8, b"\x00"))
libc.address = leak - 0x3ed8b0    # glibc-2.27 specific offset
log.info("libc base: %#x", libc.address)

Beklenen çıktı: program normal yazdırmak yerine libc pointer'ları içeren bir dizi raw byte yayar; bilinen symbol offset'ini çıkarmak libc base'ini verir, ardından bir ret2libc ya da hook overwrite önemsizdir.

Mitigation

  • glibc ≥ 2.35 _IO_FILE vtable/handle kontrollerini sıkılaştırır ve naif varyantı bozar; modern FSOP _IO_vtable_check'i sağlamalıdır.
  • Leak, &_IO_2_1_stdout_'a ulaşan bir relative write ya da partial overwrite gerektirir; PIE/ASLR çıtayı yükseltir ama bu, kendisi ASLR-yenen bir leak'tir.

References