(;′⌒`)
what is off-by-one
严格来说,off-by-one漏洞是一种特殊的溢出漏洞,指程序向缓冲区中写入时,写入的字节数超过了缓冲区本身的大小,并且只越界了一个字节。这种漏洞的产生往往与边界验证不严或字符串操作有关,当然也有可能写入的size正好就只多了一个字节:
- 使用循环语句向缓冲区中写入数据时,循环的次数设置错误导致多写入一个字节
- 字符串操作不合适,比如忽略了字符串末尾的
\x00
一般而言,单字节溢出很难利用。但因为Linux中的堆管理机制ptmalloc验证的松散型,基于Linux堆的off-by-one漏洞利用起来并不复杂,而且威力强大。需要说明的是,off-by-one
是可以基于各种缓冲区的,如栈、bss段等。但堆上的off-by-one
在CTF中比较常见。
两种常见的off-by-one例子
栅栏错误
多写入一个字节
1 | int my_gets(char *ptr,int size) |
null byte off-by-one
第二十五个字节被设置为 “\x00”
1 | int main(void) |
Asis CTF 2016 b00ks
程序分析
首先题目提供了如下功能
1 | 1. Create a book |
其中对于 create
创建了三个堆块,一个 name
块,大小为自定义;一个 description
块,大小为自定义;一个 book_ptr
块,大小为固定,用来保存当前book的信息,如下:
1 | struct book_struct |
漏洞分析
不难发现,在程序最开始读入 author_name
和修改 author_name
的时候,都存在 null byte off-by-one
漏洞。
1 | signed __int64 sub_B6D() |
那知道这一点又有什么用呢,可以看到,在保存 book_struct
结构体的时候,在数据段同时保存了这个结构体堆的地址:
1 | book_ptr = malloc(0x20uLL); |
回想起我们同时在数据段存了 author_name
,二者有这样的关系:
1 | .data:0000000000202008 off_202008 dq offset off_202008 ; DATA XREF: sub_980+17↑r |
我们不妨在内存中直接观察看看,这里是在create一个book之后的结构,可以看到boo1_struct_addr
就紧跟在author_name
之后
1 | pwndbg> x/30gx 0x555555756010 |
可以看到author_name
和book1_struct_addr
之间相差的正好是32个字节,在写入32个字节长度author_name
时,会向book1_struct_addr
写入一个字节的”\x00”,在book1
被创建后”\x00”就会被覆盖掉,所以可以通过打印author_name
来实现泄露book1_struct_addr
的地址。
漏洞利用
创建两个book
,通过null byte off-by-one
,可以使book1_struct_addr
指向book1_description
,这里需要注意的是book1的size不要设置的太小,不然当boo1_struct
低位字节被”\x00”覆盖之后不能正确落到book1_description
,然后通过编辑book1_description
来伪造boo1_struct
,这个伪造的book1_struct
中,使book1_description
指向book2_name
,从而依次覆盖掉book2_name
和book2_description
。现在我们通过edit book2就可以实现任意地址写了,但开启了got表不可写,但是但是可以改写__free_hook
。
那可以实现任意改写之后,如何拿到shell权限呢,或者说如何拿到libc基址呢。
这里的巧妙之处在于创建book2的时候,如果把size设的很高,现有的heap不足以分配,堆就会以mmap的形式进行扩展。而扩展的mmap与libc基址之间的偏移使固定的,所以我们可以根据这个固定偏移来计算出基址。
1 | LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA |
Exploit
1 | #!/usr/bin/env python |