[DreamHack] [Pwnable] Reconquista

image.png

image.png

3주가 사라졌다

제일 처음에 시도했던 10레벨 문제다. 중간에 안풀려서 Showdown(10)이나 redis_made(10)을 풀어보긴 했는데,

다 실패하고 결국 가장 많은 진척도를 보였던 Reconquista에 매달렸다.

결국 핵심 아이디어 포인트는 문제에 있었다. std::thread() 를 어떻게 활용할 것인가.

심지어, C++ 메뉴 기반 pwnable이라 루프가 스레드 안에서 동작해서 더 헤맸던 감이 있다.

분석 시작하겠다.

TLS에 std::string* 포인터 배열 200개가 배치되고,

경계 검사 없이 인덱스 증가시 OOB 쓰기로 뒤따르는 TCB에 침범한다.

이 문제는 TLS 바로 뒤에 TCB(tcbhead_t)가 연속으로 배치되는 구성이다.

tcb 헤드 앞부분의 중요 구조체를 좀 보면,

struct tcbhead_t {
    void *tcb;             // +0x00
    dtv_t *dtv;            // +0x08  ← 목표
    void *self;            // +0x10
    int multiple_threads;  // +0x18
    uintptr_t sysinfo;     // +0x20
    uintptr_t stack_guard; // +0x28  ← 스택 카나리
    ...
}

이 부분이 우리가 오버플로우로 덮고 싶은 구간이다.

문제는 0x08에서부터 덮기 시작하면 6번째 포인터(0x28)을 건드리는 시점에 stack_guard(canary)가 망가진다는 것이다.

즉, 덮을 수 있는 포인터는 최대 5개로 이 제약으로 인해 dtv 포인터 겨냥이 최선이라고 확신을 가졌다.

glibc 동작 경로는 다음과 같은데,