Skip to content

Integer Overflow & Off-by-One

CWE-190 difficulty: orta

Integer overflow, bir tamsayının tipinin sınırını aşınca “sarması”dır (wraparound). Tek başına bellek bozmaz; ancak bir boyut/uzunluk hesabını etkilediğinde, sınır kontrolünü atlatarak buffer overflow veya küçük ayırma + büyük kopyalamaya köprü olur. Off-by-one ise çoğu zaman komşu header/RBP için yeterli tek baytlık bozulmadır.

// 1) Boyut hesabında taşma → küçük ayırma, büyük kopyalama
unsigned short len = get_len(); // 65535'i aşınca sarar
char *buf = malloc(len);
memcpy(buf, src, real_len); // heap overflow
// 2) İşaretli/işaretsiz karışımı → negatif "boyut" büyük unsigned olur
int n = read_int();
if (n < MAX) memcpy(dst, src, n); // n negatifse kontrol atlanır, n->size_t dev olur
// 3) Off-by-one: <= yerine < / null byte taşması
char buf[64];
for (i = 0; i <= 64; i++) buf[i] = inp[i]; // 1 bayt taşma
// 4) Toplama/çarpma taşması
size_t total = num * size; // num*size wrap → küçük malloc
malloc(total);
for (i = 0; i < num; i++) read(fd, p + i*size, size);
TipMax (işaretsiz)Not
uint8_t255+1 → 0
uint16_t65535sık CTF
uint32_t4294967295malloc arg
int32_t2147483647negatif bypass
# Wrap hesaplama
import ctypes
ctypes.c_uint16(65535 + 5).value # 4
ctypes.c_uint32(0x100000000 + 8).value # 8
- Boyut kontrolünü sarma ile atlat → aşırı kopyala
- Off-by-one ile bitişik meta veriyi (saved rbp / chunk size) 1 bayt değiştir
("null byte poisoning" heap'te güçlüdür)
- Negatif indeks/uzunluk ile sınır dışı okuma/yazma
- num*size wrap → overlapping allocations / OOB

Zafiyet, taşan değerin sonradan bir kopyalama/ayırma/indeksleme kararında kullanılıp kullanılmadığına bağlıdır; tek başına bir overflow değil, ona giden bir adımdır.

Stack off-by-one:
saved RBP düşük baytı bozulur → leave; ret sonrası rsp kayar
→ kısmi stack pivot / ret adresine kayma
Heap null byte poison (kavramsal):
sonraki chunk size: 0x101 → 0x100 (P biti etkilenir)
→ prev_size sahte → unlink / consolidate → arbitrary write
(glibc sürümüne göre ek korumalar var)
  1. Boyut yolunu bul — malloc(n) , read(0,buf,n) , memcpy(…, n) ve n ’nin nasıl hesaplandığını Ghidra’da izle (çarpma/toplama/cast).

  2. Tip genişliğini not et — short / int / size_t karışımı ve if (n < MAX) işaretli mi?

  3. Wrap değeri seç — kontrolü geçen ama kopyalamada büyük olan n üret; local’de ASan/GDB ile OOB’u doğrula.

  4. Primitive’e çevir — oluşan overflow/UAF ile stack veya heap zincirine bağla (leak → ROP).

  5. Stabilize — sınır değerler ( 0 , MAX , MAX+1 , negatif) için tablo tut; remote ile aynı genişlikte integer kullandığından emin ol.

from pwn import *
def try_len(n):
io = process('./chall')
io.sendlineafter(b'len: ', str(n).encode())
io.send(b'A' * min(n if n > 0 else 0, 0x2000))
# crash / diff davranış
io.close()
for n in (0, 1, 0xffff, 0x10000, 0xffffffff, -1):
log.info(f'trying {n}')
try_len(n)
Terminal window
pwndbg> break malloc
Terminal window
pwndbg> commands
silent
printf "malloc size = %p\n", $rdi
continue
end